---
title: Can I use SMTP with Vercel?
description: Vercel Functions can open SMTP connections on the Node.js runtime. Learn which ports are open, why you must await the send, and when to use an email API.
url: /kb/guide/serverless-functions-and-smtp
canonical_url: "https://vercel.com/kb/guide/serverless-functions-and-smtp"
published: 2025-11-03
last_updated: 2026-08-18
authors: Vercel
related:
  - /docs/functions
  - /docs/functions/runtimes/node-js
  - /docs/routing-middleware
  - /docs/fluid-compute
  - /docs/functions/functions-api-reference/vercel-functions-package
  - /kb/guide/sending-emails-from-an-application-on-vercel
  - /docs/errors/FUNCTION_INVOCATION_TIMEOUT
  - /docs/functions/configuring-functions/duration
  - /docs/environment-variables/sensitive-environment-variables
  - /docs/workflows
  - /docs/functions/functions-api-reference
  - /kb/guide/what-can-i-do-about-vercel-serverless-functions-timing-out
  - /kb/guide/using-email-with-your-vercel-domain
install_vercel_plugin: npx plugins add vercel/vercel-plugin
---
<!-- docsgraph:related -->
## Related pages

> **For AI agents:** Follow these links to understand how this page connects to the rest of the Vercel ecosystem. For the full cross-link map (inbound, outbound, prerequisites, and semantic neighbors), see the .graph.md link below.

- [Concepts](https://vercel.com/docs/queues/concepts?from=related) — Learn delivery, retries, visibility timeouts, and deployment isolation in Vercel Queues.
- [Runtimes](https://vercel.com/docs/functions/runtimes?from=related) — Runtimes transform your source code into Functions, which are served by our CDN. Learn about the official runtimes suppo
- [How to ship an Express app on Vercel](https://vercel.com/kb/guide/ship-a-express-app-on-vercel?from=related) — Deploy an Express app to Vercel with zero configuration. Configure response streaming, middleware, cron jobs, the Bun ru
- [Why am I no longer receiving email after adding my domain to Vercel?](https://vercel.com/kb/guide/why-has-email-stopped-working?from=related) — Fix email that stopped working after adding your domain to Vercel, with a concrete MX record table and the DNS preset cl
- [How to ship a NestJS app on Vercel](https://vercel.com/kb/guide/ship-a-nestjs-app-on-vercel?from=related) — Deploy a NestJS app to Vercel with zero configuration. Learn how to ship from a template, the Nest CLI, or Git, and conf
- [Publish and subscribe to realtime data on Vercel](https://vercel.com/kb/guide/publish-and-subscribe-to-realtime-data-on-vercel?from=related) — Learn how to publish and subscribe to realtime data on Vercel with WebSockets, SSE, Redis, and Queues, and when a manage
- [How to run background jobs in Next.js](https://vercel.com/kb/guide/how-to-run-background-jobs-in-nextjs-on-vercel?from=related) — Learn the durable way to run background jobs in Next.js on Vercel with the Workflow SDK, and when to reach for Queues or

Full cross-link map for this page: [/kb/guide/serverless-functions-and-smtp.graph.md](/kb/guide/serverless-functions-and-smtp.graph.md)
<!-- /docsgraph:related -->


[Vercel Functions](https://vercel.com/docs/functions) on the Node.js runtime can send email over SMTP (Simple Mail Transfer Protocol). Your function opens an outbound connection to an external mail server, and Vercel blocks port 25 while leaving ports 465 and 587 open. A library like [Nodemailer's SMTP transport](https://nodemailer.com/smtp) connects on either one with no extra configuration. The code around that connection needs more attention than the connection itself, because a function pauses background work as soon as it responds.

This guide covers what the platform allows, how to write the send so it completes, and when an HTTP email API is the better default.

## Which SMTP ports does Vercel allow?

Vercel doesn't block outgoing SMTP connections except on port 25. No other port restrictions apply to Vercel Functions.

Vercel treats the three standard SMTP ports differently:

- **Port 25:** Blocked outbound. Mail servers use this port to relay messages to each other, which is the pattern spam relays abuse, so cloud platforms commonly block it by default. Send through 465 or 587 instead.
  
- **Port 465:** Open. Mail providers use this port for authenticated submission over implicit TLS (Transport Layer Security), where the connection is encrypted before the exchange begins. Set `secure: true` in your client.
  
- **Port 587:** Open. Mail providers use this port for authenticated submission over STARTTLS, where the connection opens in plaintext and then upgrades to TLS. Set `secure: false` and let the client negotiate the upgrade.
  

Confirm which port your mail provider expects before changing anything else, then check that your function runs on a runtime that can open the socket.

## Which runtimes support SMTP on Vercel?

SMTP needs a raw TCP (Transmission Control Protocol) connection, which narrows your options to one runtime.

Support depends on the runtime:

- **Node.js runtime:** Supports SMTP. The `net` and `tls` modules are both available, so a client can open the socket it needs. Route handlers run here by default, so nothing needs configuring.
  
- **Edge runtime:** Doesn't support SMTP. It exposes only a subset of Node.js modules (`async_hooks`, `events`, `buffer`, `assert`, and `util`), so there's no socket to open. [Migrate to Node.js](https://vercel.com/docs/functions/runtimes/node-js), which is where Next.js 16.3 and later run routes and pages regardless.
  
- **Routing Middleware:** Supports SMTP only on Node.js. It [defaults to Edge](https://vercel.com/docs/routing-middleware), and in Next.js 16 and later the renamed `proxy.ts` file runs on Node.js only. Send mail from a route handler instead, since middleware runs ahead of every request its matcher covers.
  

All three run on [Fluid compute](https://vercel.com/docs/fluid-compute), which doesn't change the modules or ports your code can use. What changes is how work continues after the response, and that determines whether a send finishes at all.

## How to send email over SMTP with Nodemailer

Point the transporter at a `host` from a third-party mail provider, since Vercel doesn't run a mail server. This handler creates the transporter, awaits the send, and returns the message ID:

```tsx
import nodemailer from 'nodemailer';

const transporter = nodemailer.createTransport({
  host: process.env.SMTP_HOST,
  port: 465,
  secure: true, // true for port 465, false for port 587
  auth: {
    user: process.env.SMTP_USER,
    pass: process.env.SMTP_PASSWORD,
  },
});

export async function POST() {
  try {
    const info = await transporter.sendMail({
      from: 'Acme <hello@your-domain.com>',
      to: ['delivered@your-domain.com'],
      subject: 'Hello world',
      html: '<strong>It works</strong>',
    });

    return Response.json({ messageId: info.messageId });
  } catch (error) {
    console.error(error);
    return Response.json({ error: 'Failed to send' }, { status: 500 });
  }
}
```

Creating the transporter at module scope lets a warm instance reuse it, because Fluid compute can share one instance and its global state across invocations. The `await` on `sendMail` is what holds the handshake inside the invocation.

Two limits come with this handler:

- **You own the connection:** The socket lifetime and the handshake are yours to manage, on every invocation.
  
- **Nothing retries for you:** A dropped connection or a rejected recipient surfaces once, in the `catch` block. The message is gone unless your own code sends it again.
  

Use raw SMTP when you need that level of protocol control, and an email API when you'd rather not own either. Whichever you pick, the send has to finish before the response goes out.

## Why an SMTP send needs an explicit await

Once a function sends its response, background work pauses and resumes only when the function is invoked again. A send still in flight at that point stops partway through the exchange.

Local development hides this, because your own process keeps running after the response and the same code finishes. Confirm the behavior on a preview deployment rather than locally.

Two patterns keep the send inside the invocation:

- **Await the send:** Await `sendMail` (or your provider's send call) before returning a response, as in the handler above. Reach for this pattern first.
  
- **Schedule it with** `**after**` **or** `**waitUntil**`**:** Use `after` from `next/server` on Next.js 15.1 and later, or [the](https://vercel.com/docs/functions/functions-api-reference/vercel-functions-package) [`waitUntil`](https://vercel.com/docs/functions/functions-api-reference/vercel-functions-package) [method](https://vercel.com/docs/functions/functions-api-reference/vercel-functions-package) from `@vercel/functions`. Either one extends the handler's lifetime for the sending promise, and the instance stays alive under Fluid compute until it settles.
  

Scheduling the send lets you respond before it finishes:

```tsx
import { after } from 'next/server';
import { transporter } from '@/lib/mailer';

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

  after(async () => {
    await transporter.sendMail({
      from: 'Acme <hello@your-domain.com>',
      to: [email],
      subject: 'Welcome',
      html: '<strong>Your account is ready</strong>',
    });
  });

  return Response.json({ ok: true });
}
```

Work inside `after` still counts toward the function's maximum duration, so this shortens the response, not the send.

## When to use an email API instead of SMTP

An HTTP request fits the shape of a function better than a session does. One POST carries the message and completes inside a single invocation, so you can check its status before you respond. Retries and per-message delivery logs stay on the provider's side.

Most [mail services Vercel recommends](https://vercel.com/kb/guide/sending-emails-from-an-application-on-vercel) expose an HTTP API alongside their SMTP endpoint, including Postmark, Resend, SendGrid, AWS SES, and MailChimp.

Set it up in three steps:

1. Install the [Resend Marketplace integration](https://vercel.com/marketplace/resend), which provisions `RESEND_API_KEY` in your project, or start from the [React Email with Resend](https://vercel.com/templates/next.js/react-email-resend) template.
   
2. Add a route handler that sends the message.
   
3. Keep the key server-side by calling the provider from your route handler, a Server Component, or a Server Action, and never prefixing the variable with `NEXT_PUBLIC_`.
   

The route handler is a single call, [documented by Resend](https://resend.com/docs/send-with-vercel-functions) for Vercel Functions:

```tsx
import { Resend } from 'resend';

const resend = new Resend(process.env.RESEND_API_KEY);

export async function POST() {
  const response = await resend.emails.send({
    from: 'Acme <onboarding@resend.dev>',
    to: ['delivered@resend.dev'],
    subject: 'Hello world',
    html: '<strong>It works!</strong>',
  });

  return Response.json(response, {
    status: response.error ? 500 : 200,
  });
}
```

The `error` field comes back in the same request. A rejected send surfaces while the invocation is still running, not days later in a support ticket.

## Other causes of SMTP send failures and how to fix them

If mail still doesn't arrive, work through these four causes in order.

### The SMTP handshake times out

An invocation that runs past its maximum duration returns a 504 [`FUNCTION_INVOCATION_TIMEOUT`](https://vercel.com/docs/errors/FUNCTION_INVOCATION_TIMEOUT) [error](https://vercel.com/docs/errors/FUNCTION_INVOCATION_TIMEOUT). Before the message itself goes out, an SMTP session exchanges a greeting, authentication, the sender address, and each recipient with the mail server. Every one of those steps waits for a reply, so any of them can stall and hold the invocation open until it times out.

A slow mail server can need more time than the route's default allows. Set `maxDuration` on the route, up to 800 seconds on Pro and Enterprise plans:

```tsx
export const maxDuration = 60; // seconds
```

Pair that with a connection timeout in your SMTP client, so a dead host fails fast rather than consuming the whole budget. See [configuring maximum duration](https://vercel.com/docs/functions/configuring-functions/duration) for the current defaults and maximums.

### Credentials come back undefined

Environment variables are read from `process.env` at runtime, and a new value takes effect only after you redeploy. Add the SMTP username and password as [sensitive environment variables](https://vercel.com/docs/environment-variables/sensitive-environment-variables), redeploy, then read them where the handler runs rather than caching them during the build.

If a value is present locally and missing on Vercel, confirm it's set for the environment you deployed to.

### The provider accepts the message but it never arrives

A 200 from your route means the provider accepted the request, not that the recipient's mail server accepted the message. Check the provider's own delivery log for the per-message status.

Verify your sending domain with the provider, then confirm its SPF (Sender Policy Framework) and DKIM (DomainKeys Identified Mail) records resolve. Unauthenticated mail is where deliverability usually breaks.

### The email pipeline outlives the request

Drip sequences, retries spread over hours, and flows that wait for a webhook don't fit inside a request-response function, whichever protocol they use.

Move a multi-step or scheduled email sequence to [Vercel Workflows](https://vercel.com/docs/workflows). A workflow pauses, resumes, and maintains state for minutes to months, so the schedule lives in durable storage instead of an open socket.

Once the send pattern matches how long the work actually runs, the remaining step is deploying it.

## Next steps

Pick the path that matches your requirement, whether that's raw SMTP for protocol control or an HTTP API for everything else. From there, [start a new project](https://vercel.com/new) to deploy your own route, or [clone the Resend template](https://vercel.com/templates/next.js/react-email-resend) for a working email route on the first deployment.

## Related resources

- [Sending emails on Vercel](https://vercel.com/kb/guide/sending-emails-from-an-application-on-vercel)
  
- [Vercel Functions](https://vercel.com/docs/functions)
  
- [Node.js runtime](https://vercel.com/docs/functions/runtimes/node-js)
  
- [Functions API reference](https://vercel.com/docs/functions/functions-api-reference)
  
- [Configuring maximum duration](https://vercel.com/docs/functions/configuring-functions/duration)
  
- [Fixing function timeouts](https://vercel.com/kb/guide/what-can-i-do-about-vercel-serverless-functions-timing-out)
  

## Frequently asked questions

### Does Vercel block port 25?

Yes. Vercel blocks outbound SMTP on port 25 while leaving other SMTP ports open, including 465 for implicit TLS and 587 for STARTTLS submission. Port 25 carries server-to-server relay traffic that spam relays abuse, which is why cloud platforms block it by default. Use the submission port your provider documents.

### Why does my Nodemailer code work locally but fail on Vercel?

The usual cause is a send the handler never waits on. Vercel pauses background work once the response is sent, so the message stops mid-exchange without an error. Await `sendMail` before returning, or schedule it with `after` or `waitUntil`, and confirm the route runs on the Node.js runtime.

### Does Vercel provide an email service for my domain?

No. Vercel is a deployment platform and provides no mail service for domains bought through it or transferred into it. To receive mail, add `MX` (mail exchange) records for a third-party provider, as covered in [email on Vercel domains](https://vercel.com/kb/guide/using-email-with-your-vercel-domain). For sending, install a provider from the Vercel Marketplace.

### Can I reuse an SMTP connection between function invocations?

Partly. Create the transporter at module scope, and a warm instance reuses it, because Fluid compute lets multiple invocations share one instance and its global state. Instances are paused when traffic stops, so your client has to reconnect on a cold start. Never assume an open socket persists between invocations.