Back to blog
VercelEmailTutorialServerless

Vercel Email: How to Send Email from Vercel Functions

Send email from Vercel using Serverless Functions or Edge Functions. TypeScript examples, environment variables, cron jobs, and local testing. Works with or without Next.js.

SendPigeon TeamMarch 11, 2026Updated March 27, 20267 min read

To send email from Vercel, use an HTTP-based email API (not SMTP) in a Serverless or Edge Function. Vercel blocks outbound SMTP connections, so libraries like Nodemailer won't work — but any email API that sends over HTTP works perfectly. Create a function in the api/ directory, install an email SDK, and call the send method.

TL;DR

Quick setup:

  1. npm install sendpigeon
  2. Add SENDPIGEON_API_KEY in Vercel project settings
  3. Create a function in api/ and call pigeon.send()

Best for: Contact forms, transactional emails, scheduled digests, webhook-triggered emails.


Install the SDK

npm install sendpigeon

Set Up Environment Variables

Add your API key in the Vercel dashboard under Project Settings > Environment Variables:

SENDPIGEON_API_KEY=sp_live_your_key_here

For local development, create .env.local at your project root:

SENDPIGEON_API_KEY=sp_live_your_key_here

Or pull your Vercel environment variables locally:

vercel env pull .env.local

Add .env.local to .gitignore. Never commit API keys.


Send Email from a Vercel Serverless Function

Create a function in the api/ directory. Each file becomes an endpoint — api/send-email.ts maps to /api/send-email.

// api/send-email.ts
import { SendPigeon } from "sendpigeon";

const pigeon = new SendPigeon(process.env.SENDPIGEON_API_KEY!);

export async function POST(request: Request) {
  const { to, name } = await request.json();

  const { data, error } = await pigeon.send({
    from: "hello@yourdomain.com",
    to,
    subject: `Hey ${name}!`,
    html: `<h1>Welcome</h1><p>Thanks for signing up.</p>`,
  });

  if (error) {
    return Response.json({ error: error.message }, { status: error.status ?? 500 });
  }

  return Response.json({ emailId: data.id });
}

Using Next.js on Vercel? See our dedicated Next.js email guide — it covers Server Actions, API routes, and React Email.


Send Email from Vercel Edge Functions

Edge Functions run on V8 isolates closer to your users. The SendPigeon SDK works here because it uses the Fetch API — no SMTP or native Node.js modules.

// api/send-email-edge.ts
import { SendPigeon } from "sendpigeon";

export const config = {
  runtime: "edge",
};

const pigeon = new SendPigeon(process.env.SENDPIGEON_API_KEY!);

export async function POST(request: Request) {
  const { to, subject, html } = await request.json();

  const { data, error } = await pigeon.send({
    from: "hello@yourdomain.com",
    to,
    subject,
    html,
  });

  if (error) {
    return Response.json({ error: error.message }, { status: error.status ?? 500 });
  }

  return Response.json({ emailId: data.id });
}

Contact Form Example

A complete contact form handler with validation:

// api/contact.ts
import { SendPigeon } from "sendpigeon";

const pigeon = new SendPigeon(process.env.SENDPIGEON_API_KEY!);

export async function POST(request: Request) {
  const { name, email, message } = await request.json();

  if (!name || !email || !message) {
    return Response.json({ error: "Missing fields" }, { status: 400 });
  }

  // Notify yourself
  const { error } = await pigeon.send({
    from: "contact@yourdomain.com",
    to: "you@yourdomain.com",
    subject: `Contact form: ${name}`,
    html: `
      <p><strong>From:</strong> ${name} (${email})</p>
      <p><strong>Message:</strong></p>
      <p>${message}</p>
    `,
    replyTo: email,
  });

  if (error) {
    return Response.json({ error: "Failed to send" }, { status: 500 });
  }

  return Response.json({ success: true });
}

Schedule Emails with Vercel Cron Jobs

Vercel Cron Jobs trigger a GET request to your function on a schedule. Configure in vercel.json:

{
  "crons": [
    {
      "path": "/api/daily-digest",
      "schedule": "0 9 * * *"
    }
  ]
}
// api/daily-digest.ts
import { SendPigeon } from "sendpigeon";

const pigeon = new SendPigeon(process.env.SENDPIGEON_API_KEY!);

export async function GET(request: Request) {
  // Verify cron secret
  const authHeader = request.headers.get("authorization");
  if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
    return new Response("Unauthorized", { status: 401 });
  }

  // Fetch users and send digest
  const users = await getActiveUsers();

  await pigeon.sendBatch(
    users.map((user) => ({
      from: "digest@yourdomain.com",
      to: user.email,
      subject: "Your daily digest",
      html: `<h1>Hey ${user.name}</h1><p>Here's what happened today...</p>`,
    }))
  );

  return Response.json({ sent: users.length });
}

Cron jobs only run on production deployments. Set a CRON_SECRET environment variable — Vercel sends it as an Authorization header so your function can verify the request is legitimate.


Handle Errors

The SDK returns { data, error } instead of throwing:

const { data, error } = await pigeon.send({
  from: "hello@yourdomain.com",
  to: "user@example.com",
  subject: "Hello",
  html: "<p>Hello!</p>",
});

if (error) {
  console.error(`Email failed: ${error.message}`);

  if (error.status === 429) {
    // Rate limited — retry later
  }

  return Response.json({ error: error.message }, { status: error.status ?? 500 });
}

return Response.json({ emailId: data.id });

Why SMTP (Nodemailer) Doesn't Work on Vercel

Vercel Serverless Functions run in a sandboxed environment that blocks outbound SMTP connections (ports 25, 465, 587). If you try to use Nodemailer with an external SMTP server, your function will hang and eventually time out.

Symptoms:

  • sendMail() never resolves
  • Function times out after 10 seconds
  • ECONNREFUSED or ETIMEDOUT errors

Fix: Use an email API that sends over HTTP (port 443) instead of SMTP. HTTP is not blocked on Vercel. Replace Nodemailer with an SDK like SendPigeon, Resend, or Postmark — all of which use HTTP under the hood.

// This does NOT work on Vercel:
const transport = nodemailer.createTransport({
  host: "smtp.example.com",
  port: 587, // Blocked
});

// This works on Vercel:
const pigeon = new SendPigeon(process.env.SENDPIGEON_API_KEY!);
await pigeon.send({ from, to, subject, html }); // Uses HTTP

Troubleshooting

Function times out when sending email

Most likely using SMTP (Nodemailer). Switch to an HTTP-based email API — see section above.

Email sends in development but not in production

Check that your SENDPIGEON_API_KEY environment variable is set for the Production environment in Vercel settings. By default, environment variables are only set for Development.

Emails land in spam

Set up DKIM, SPF, and DMARC on your sending domain. Without authentication, most providers will flag your email. Use our deliverability checker to verify your setup.

Cron job not triggering

Cron jobs only run on production deployments (not preview or development). Also verify your vercel.json cron schedule syntax and that the CRON_SECRET is set.


Test Locally

Test your emails locally before deploying:

npx @sendpigeon-sdk/cli dev

This starts a local email server. All emails are caught and viewable at localhost:4100. No emails reach real inboxes.

See our local email testing guide for full setup.


Frequently Asked Questions

Can I send email from Vercel without a framework?

Yes. Vercel supports standalone Serverless Functions without Next.js or any framework. Place a TypeScript file in the api/ directory and export a named HTTP method handler (like POST) — Vercel deploys it as a function automatically.

Does SendPigeon work on Vercel Edge Functions?

Yes. The SendPigeon SDK uses the Fetch API under the hood, so it runs in both the Node.js and Edge runtimes. Unlike SMTP-based libraries like Nodemailer, there are no native module dependencies to worry about.

How do I store my API key on Vercel?

Add SENDPIGEON_API_KEY as an environment variable in your Vercel project settings (Settings > Environment Variables). You can scope it per environment — Production, Preview, or Development. Access it in your function with process.env.SENDPIGEON_API_KEY.

Can I schedule emails on Vercel?

Yes. Use Vercel Cron Jobs to trigger a Serverless Function on a schedule. Define the schedule in vercel.json using cron syntax. The function receives a GET request and can send emails, digests, or reports on schedule.

What's the difference between Vercel Serverless and Edge Functions?

Serverless Functions run full Node.js with access to all npm packages. Edge Functions run on V8 isolates with Web Standard APIs — faster cold starts but no access to native Node.js modules like fs or net. Both work with SendPigeon since the SDK only uses fetch.


Next Steps


Other Platforms

Deploying elsewhere? We have guides for:

See all framework guides.