Emails
Transactional emails with React Email and Resend. Server-only API key access, typed send helpers, included templates for purchase delivery and account flows.
What's enforced
Email templates are React components in emails/, compiled and sent through Resend. The Resend API key is read from process.env.RESEND_API_KEY server-only; the email send always runs in a Server Action or webhook handler, never in client code. Every send goes through a typed helper in lib/resend/send.ts so there is one canonical send surface to audit.
Setup
- Create an account at resend.com.
- Add your API key to
.env.localasRESEND_API_KEY. The Zod env validator (lib/env.ts) enforces it starts withre_. - Verify your sending domain in the Resend dashboard. Until the domain is verified, email goes through Resend's shared
onboarding@resend.devsender and gets flagged by spam filters at scale. - Update the
fromandsupportEmailaddresses inconfig.ts:
email: {
from: 'YourSaaS <noreply@yoursaas.com>',
supportEmail: 'support@yoursaas.com',
}
Included templates
Templates live in the emails/ directory:
| Template | File | Trigger |
|---|---|---|
| Welcome | emails/welcome.tsx | Sent after signup |
| Email verification | emails/verify-email.tsx | Sent during sign-up confirmation |
| Password reset | emails/reset-password.tsx | Triggered by resetPassword Server Action |
| Purchase delivery | emails/purchase-delivery.tsx | Sent by the Stripe webhook on checkout.session.completed, delivers repo access to the buyer |
| Purchase notification | emails/purchase-notification.tsx | Sent in parallel to the admin email so you see the purchase in real time |
The purchase delivery + notification pair is the canonical post-purchase pattern: one email for the buyer (delivery), one for you (notification). The webhook handler fires both in parallel via Promise.all so a slow admin SMTP does not delay buyer delivery.
Sending an email
import { sendWelcomeEmail } from '@/lib/resend/send'
await sendWelcomeEmail({
email: 'user@example.com',
name: 'Jane',
})
Every helper in lib/resend/send.ts is typed against the props its template expects, so a missing or wrongly-typed prop fails at the type level rather than at the SMTP layer.
For the deep walkthrough on transactional email patterns (deliverability, SPF/DKIM/DMARC, idempotent sends), see How to send emails in Next.js with React Email and Resend (2026).
Previewing templates locally
npx email dev
This boots the React Email dev server at http://localhost:3001 and renders every template with mocked props. Iterate on the design without sending real email.
Creating a new template
- Create a new file in
emails/(use the included templates as the shape reference):
import { Html, Head, Body, Text } from '@react-email/components'
export default function MyEmail({ name }: { name: string }) {
return (
<Html>
<Head />
<Body>
<Text>Hello {name}.</Text>
</Body>
</Html>
)
}
- Add a typed send helper in
lib/resend/send.ts:
import MyEmail from '@/emails/my-email'
export async function sendMyEmail(props: { email: string; name: string }) {
await resend.emails.send({
from: config.email.from,
to: props.email,
subject: 'Subject line',
react: MyEmail({ name: props.name }),
})
}
- Call the helper from the Server Action or webhook that should trigger it.
Full step-by-step in Recipes: Add an email template.