Shipping emails isn't hard. Shipping emails that are reliable, secure, and don't land in spam is where teams lose weeks.
This is a practical checklist for using React Email with Resend in a Next.js app — the stuff you actually need before you call it production.
The baseline stack
- Next.js (App Router)
- React Email for template rendering
- Resend as the SMTP/API provider
npm install resend react-email @react-email/components1) Render server-side (and keep the client out of it)
The safest setup is: render and send emails on the server only. Don't bundle templates into client components.
import { Resend } from "resend";
import WelcomeEmail from "@/emails/welcome";
const resend = new Resend(process.env.RESEND_API_KEY);
export async function POST(req: Request) {
const { email, name } = await req.json();
const { error } = await resend.emails.send({
from: "Acme <hello@acme.com>",
to: email,
subject: `Welcome, ${name}!`,
react: WelcomeEmail({ name }),
});
if (error) return Response.json({ error }, { status: 500 });
return Response.json({ ok: true });
}2) Environment variables: be strict
Production email failures are usually boring: missing env vars, wrong from address, or secrets in the wrong place.
RESEND_API_KEYonly on the server- A verified sending domain (not a random Gmail)
- Separate dev and prod "from" names to avoid confusion
export function requireEnv(name: string) {
const value = process.env[name];
if (!value) throw new Error("Missing env var: " + name);
return value;
}
export const RESEND_API_KEY = requireEnv("RESEND_API_KEY");3) Validate inputs and props
The email template is code — treat its props like an API contract. Validate them before sending.
import { z } from "zod";
export const welcomeEmailSchema = z.object({
email: z.string().email(),
name: z.string().min(1).max(80),
});
export type WelcomeEmailInput = z.infer<typeof welcomeEmailSchema>;4) Deliverability basics (do these or don't bother)
- Set up SPF and DKIM for your domain
- Add DMARC (start with
p=none, then tighten) - Use a consistent from address like
hello@yourdomain.com - Include a plain-text fallback if your setup requires it
5) Make sends idempotent (avoid double emails)
Most duplicate emails come from retries: webhook delivery, background jobs, users double-clicking, or serverless replays.
Tie each send to a unique business event (userId + eventId) and store a send record before you hit the provider.
Minimal pattern
- Generate a deterministic key (ex:
welcome:{userId}) - Check DB: already sent? return early
- Write "sending" state
- Send
- Write "sent" state + provider message id
6) Test what matters: rendering and links
You don't need a perfect email test suite. You need a few high-signal checks that prevent embarrassing regressions.
- Snapshot the rendered HTML (basic regression coverage)
- Assert every CTA link exists and uses HTTPS
- Assert the preview text is present (it affects opens)
import { render } from "@react-email/render";
import WelcomeEmail from "../welcome";
test("welcome email renders a dashboard link", () => {
const html = render(WelcomeEmail({ name: "Iso", loginUrl: "https://acme.com" }));
expect(html).toContain("https://acme.com");
});Production quick-check (copy/paste)
- Domain verified + SPF/DKIM/DMARC configured
- API routes are server-only; no templates in the client bundle
- Input validation on every send
- Idempotency key for every transactional email
- At least one render/link test per template
Recommended
SaaS Essentials Pack
21+ Templates · 60+ Variations. One-time purchase, lifetime updates.
If you want the fast path: start with a small set of battle-tested templates (welcome, verification, password reset, invoice, trial ending) and iterate from there.