Skip to content
Next.js+Resend
Stack Integration

Next.js + Resend: Transactional Email Setup

Resend is the cleanest transactional email integration for Next.js App Router — React email templates, a simple API, and a dedicated sender domain in minutes.

Use Cases
  1. Welcome and onboarding emails triggered on user signup
  2. Stripe webhook → confirmation email on payment success
  3. Password reset and magic link emails
  4. Notification digests from a Next.js cron or API route
Implementation

Create a Next.js Route Handler (app/api/send/route.ts). Import Resend, initialize with RESEND_API_KEY (server-only env var). Use react-email for typed React templates. Call resend.emails.send() from server components and API routes only — never client-side. Verify the sender domain in Resend dashboard and add the DKIM/SPF records to your DNS before going to production.

In detail

How the pieces connect

The split is clean: the Resend SDK is server-only, so every resend.emails.send() call lives behind a Route Handler (app/api/.../route.ts), a Server Action, or a Server Component — never a Client Component. RESEND_API_KEY is read from process.env on the server, so the key never ships in the client bundle.

Templates are React. With react-email you write components in emails/ and pass them straight to the react field of send():

import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_API_KEY);
await resend.emails.send({
  from: 'Acme <hello@yourdomain.com>',
  to: user.email,
  subject: 'Welcome',
  react: <WelcomeEmail name={user.name} />,
});

Resend renders the JSX to HTML server-side, so the same prop-driven component pattern you use for pages drives your emails.

Production gotchas

The failures here are rarely in the send() call itself.

  • Domain + DNS first. Sending from an unverified domain works in the dashboard sandbox but bounces or lands in spam in production. Verify the domain and add the DKIM and SPF records (plus the return-path/MX entry Resend gives you) before launch. DNS propagation can take hours — do it early.
  • The Edge runtime. If a Route Handler is pinned to export const runtime = 'edge', confirm the SDK path works; the default Node.js runtime is the safe choice for the Resend SDK and Stripe verification.
  • Don't block the request. Email send is network I/O. In a signup handler, await it before returning your response, or move it off the hot path — a slow SMTP-side delay shouldn't stall the user's redirect.
  • Idempotency. Pass an idempotencyKey to send() so a retried request doesn't double-send the same email.

Stripe webhook → email, done right

The "send a receipt on payment" pattern is where most bugs hide, because the trigger is an untrusted external POST.

Verify the signature before you do anything. In the webhook Route Handler, read the raw body (not the parsed JSON) and call stripe.webhooks.constructEvent(rawBody, sig, endpointSecret). On the App Router you get the raw body with await req.text() — parsing it first breaks signature verification.

Then guard against duplicates: Stripe retries webhooks, so the same checkout.session.completed event can arrive more than once. Record the Stripe event.id (or the order id) before sending, and skip the email if you've already processed it. Combine that with Resend's idempotencyKey and a single payment can't produce two receipts. Return a 2xx quickly so Stripe stops retrying.

When this combo fits — and when it doesn't

Next.js + Resend fits transactional, app-triggered mail: welcome and verification emails, password resets, magic links, payment receipts, and low-volume notification digests fired from an API route or cron. React templates keep the markup in one language and one repo, which is the real win for a small team.

It's the wrong tool for bulk marketing campaigns with list management, segmentation, and unsubscribe handling — that's a dedicated ESP's job, and you'd be rebuilding compliance plumbing by hand. It's also not a fit if you need to send from deep inside a long-running background worker outside the Next.js deployment; at that point a queue (and a standalone worker calling the same SDK) is the cleaner architecture. For the common case — a Next.js app that needs to email its own users — the integration is hard to beat on setup time.

Other integration guidesView all →
Related

Need this built?