# How to Send Email from Next.js in 2026: Route Handlers, Server Actions, and the Pitfalls

> Send email from Next.js on the server only — a route handler or server action calling an email API over HTTPS (SMTP is a liability in serverless: connection setup per invocation, ports sometimes blocked, timeouts). Keep the API key in server-side env (never NEXT_PUBLIC_), use idempotency keys so retries can't double-send, and move non-critical sends out of the request path with after(). A welcome email should never make signup slower.

Source: https://www.mailblastr.com/blog/send-email-nextjs
Published: 2026-08-02 · Last reviewed: 2026-08-02 · Tags: nextjs, email-api, developers, serverless

---

Every Next.js app eventually sends email — signup confirmations, magic links, contact forms, receipts. The App Router gives you three good places to do it and several tempting-but-wrong ones. Here are the patterns that hold up, using plain `fetch` against an email API (the examples use [MailBlastr's `/emails` endpoint](https://www.mailblastr.com/docs/api/emails-send); the shapes port to any provider).

## Rule zero: server-side only

Your email API key authorizes sending as your domain. It must live in server-only environment variables — `MAILBLASTR_API_KEY`, **never** `NEXT_PUBLIC_*` — and be used only from route handlers, server actions, or other server code. A "contact form that emails from the client" is a stolen key in waiting.

## Pattern 1: route handler (API endpoint)

The classic shape — a `POST /api/contact` your client calls:

```ts
// app/api/contact/route.ts
import { NextResponse } from 'next/server';

export async function POST(req: Request) {
  const { name, email, message } = await req.json();
  if (!email || !message) {
    return NextResponse.json({ error: 'missing fields' }, { status: 422 });
  }

  const res = await fetch('https://www.mailblastr.com/api/emails', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.MAILBLASTR_API_KEY}`,
      'Content-Type': 'application/json',
      'Idempotency-Key': crypto.randomUUID(),
    },
    body: JSON.stringify({
      domain: 'acme.com',
      from: 'Acme Contact <contact@acme.com>',
      to: ['support@acme.com'],
      reply_to: email,
      subject: `Contact form: ${name}`,
      text: message,
    }),
  });

  if (!res.ok) return NextResponse.json({ error: 'send failed' }, { status: 502 });
  return NextResponse.json({ ok: true });
}
```

Notes that save debugging time: set `reply_to` to the visitor (so support replies go to them, not to your own domain), validate before sending, and rate-limit public forms — an unprotected contact endpoint becomes someone's spam cannon.

## Pattern 2: server action (forms without an API route)

```ts
// app/signup/actions.ts
'use server';

export async function signup(formData: FormData) {
  const user = await createUser(formData);

  await fetch('https://www.mailblastr.com/api/emails', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.MAILBLASTR_API_KEY}`,
      'Content-Type': 'application/json',
      // Business-event key: retries and double-submits can't double-send.
      'Idempotency-Key': `welcome-${user.id}`,
    },
    body: JSON.stringify({
      domain: 'acme.com',
      from: 'Acme <hello@acme.com>',
      to: [user.email],
      subject: 'Welcome to Acme',
      html: `<p>You're in, ${user.name}.</p>`,
    }),
  });

  redirect('/onboarding');
}
```

The important line is the [idempotency key](https://www.mailblastr.com/docs/emails/idempotency) derived from the business event: `welcome-${user.id}` means the welcome email for this user can only ever send once, no matter how many times the action retries.

## Pattern 3: `after()` — don't block the response

A welcome email should not add 300ms to signup. Next 15's `after()` runs work once the response has streamed:

```ts
import { after } from 'next/server';

export async function signup(formData: FormData) {
  const user = await createUser(formData);
  after(() => sendWelcomeEmail(user));   // fires post-response
  redirect('/onboarding');
}
```

Use it for nice-to-have mail (welcomes, notifications). Keep *critical* sends — password resets, magic links — in the request path, where a failure can surface to the user as "try again" instead of silently never arriving.

## The pitfalls checklist

- **SMTP libraries in serverless.** Connection setup per invocation, blocked ports on some hosts, pooling that assumes a persistent process. [The API-vs-SMTP breakdown](https://www.mailblastr.com/blog/email-api-vs-smtp) covers why one HTTPS call is the right fit.
- **Dev-mode double sends.** Strict Mode double-invokes effects; never send from `useEffect`. Sends belong in actions/handlers, and idempotency keys make accidental repeats harmless.
- **Unverified domains.** The API will 422 until your sending domain passes [DKIM/SPF/DMARC verification](https://www.mailblastr.com/docs/add-a-domain) — do that before writing code, it's the only step with DNS propagation wait.
- **No delivery feedback.** Fire-and-forget works until a user says "I never got the reset link." Wire [bounce and delivery webhooks](https://www.mailblastr.com/blog/email-webhooks-guide) to a route handler so your app knows.
- **Templates in string literals.** Fine for day one; move to [managed templates](https://www.mailblastr.com/docs/emails/send-test) or a React Email pipeline when marketing wants to edit copy without a deploy.

The complete working setup — env, domain verification, first send, webhooks — is in the [Next.js quickstart](https://www.mailblastr.com/docs/send-with/nextjs), and the [free tier](https://www.mailblastr.com/install) (3,000 emails/month) covers a side project's entire email volume without a card.


## Frequently asked questions

**Can I send email directly from a Next.js client component?**

No — and any setup that appears to is leaking credentials. Email must be sent from server code (route handler, server action, or server-side function), where your API key lives in environment variables the browser never sees. A client component should call your server endpoint, which then calls the email API.

**Should I use SMTP (nodemailer) or an email API in Next.js?**

An HTTPS API. Serverless functions pay SMTP's multi-round-trip connection setup on every cold invocation, some platforms block SMTP ports outright, and nodemailer's connection pooling assumes a long-lived process that serverless doesn't give you. One HTTPS request fits the model exactly.

**How do I send email without slowing down the page response?**

Use next/server's after() (or waitUntil on the edge runtime) to run the send after the response streams — the user's signup completes instantly and the welcome email goes out milliseconds later. For anything critical (password resets), keep it in the request path so you can surface failures.

**Why did my email send twice?**

Retries — yours, React's, or the platform's. Networks time out after the send succeeded, users double-click submit, and dev-mode double-invocation can fire effects twice. Pass an Idempotency-Key derived from the business event (like `signup-{userId}`) so the provider deduplicates; retries then return the original result instead of sending again.

**Does this work on the Edge runtime?**

Yes — an HTTPS email API is fetch-based, so it works from Edge route handlers and middleware-adjacent code where Node APIs (and SMTP libraries) don't. Keep the API key in server env and the call pattern is identical.
