Prompt
What's a good transactional email architecture for Next.js?
Latest observation
A solid transactional email setup for Next.js usually means:
- Never send emails directly from the client
- Use a server-side “email service” layer
- Queue or at least decouple sending from the request
- Use a real provider like Resend, Postmark, SendGrid, Amazon SES, etc.
- Separate templates from business logic
Recommended architecture
1) Client → Next.js server action / route handler
The frontend submits something like:
- password reset request
- order confirmation
- signup verification
- support contact form
The client calls a server action or API route.
2) Server validates and creates the email job
On the server:
- validate input
- authenticate/authorize if needed
- build an email payload
- persist an event/job if reliability matters
At this point, do not render/send the email in the client.
3) Queue the email
Best practice is to enqueue the email to a background worker.
Examples:
- BullMQ + Redis
- Cloud Tasks
- SQS
- Inngest
- Trigger.dev
- Upstash QStash
- Vercel Cron + DB polling for smaller apps
Why queue?
- avoids slowing down the user request
- retries on provider failure
- prevents duplicate/partial sends from request timeouts
- gives you observability and auditability
4) Worker sends via email provider
A worker process consumes jobs and sends through provider SDK/API.
The worker should:
- load template
- inject dynamic data
- send email
- record success/failure
- retry with backoff on transient errors
5) Track status in DB
Store email send attempts and final state:
queuedsendingsentfailed
This helps with:
- debugging
- support
- retries
- idempotency
Good practical stack choices
Small/medium app
- Next.js route handlers
- Resend or Postmark
- No queue initially
- DB record + send inline if volume is low
This is simple and often enough for:
- contact forms
- low-volume notifications
- MVPs
Production / higher reliability
- Next.js route handlers or server actions
- Database-backed job table
- Worker on separate process
- Provider: Postmark / SES / Resend
- Email templates in React + React Email
- Retry logic + idempotency keys
Suggested folder structure
src/
app/
api/
emails/
send/route.ts
emails/
templates/
welcome.tsx
reset-password.tsx
send.ts
render.ts
lib/
queue.ts
email-provider.ts
db.ts
Key implementation rules
Keep templates reusable
Use a component-based email system like:
- React Email
- or plain HTML templates
This lets you:
- preview locally
- share components
- keep styling manageable
Use a dedicated sender module
Example conceptually:
sendWelcomeEmail(user)sendPasswordResetEmail(user, token)sendReceiptEmail(order)
This keeps business logic out of route handlers.
Make send operations idempotent
If a request is retried, you don’t want duplicate emails.
Typical approach:
- create an
email_jobstable - include a unique key like
type + userId + orderId - worker skips if already sent
Verify provider domain/auth
Set up:
- SPF
- DKIM
- DMARC
- custom sending domain
Without this, deliverability suffers.
Avoid sending from Edge runtime unless supported
Many email SDKs need Node APIs, so use:
- Node runtime for route handlers/workers unless your provider explicitly supports Edge.
Example flow
Password reset:
- User clicks “Forgot password”
- Next.js route creates reset token
- App stores token and queues
password_resetemail - Worker renders email template with reset link
- Provider sends email
- DB marks job as sent
When to send inline vs queue
Inline is okay if:
- low traffic
- email is non-critical
- you can tolerate occasional latency
- you don’t mind retrying manually
Queue is better if:
- the email is important
- you need reliability
- you expect traffic spikes
- you want retries and monitoring
My default recommendation
If you’re building a serious Next.js app:
- Next.js route handler/server action
- React Email templates
- Postmark or Resend
- Queued jobs via Inngest / QStash / BullMQ
- DB audit log for email status
That gives a good balance of simplicity and reliability.
If you want, I can sketch:
- a minimal MVP setup, or
- a production-ready architecture with code examples.