Email is configured by EMAIL_PROVIDER, with credentials validated per provider at boot:
| Provider | Use | Required env |
|---|---|---|
preview | Development default: captures emails, optionally opens a browser preview tab | none |
none | Explicit opt-out: nothing sends (sign-up verification will not work) | none |
resend | Production | RESEND_API_KEY |
plunk | Production | PLUNK_API_KEY |
ses | Production (AWS) | AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY |
Production refuses preview; none is allowed as a deliberate choice for demos.
The auth flows: sign-up verification, password reset, and magic links. Plus admin notifications: contact form submissions and newsletter sign-ups route to EMAIL_ADMIN_TO when configured.
Sender identity comes from EMAIL_FROM (display name and address), with EMAIL_REPLY_TO for responses.
With the default preview provider and EMAIL_PREVIEW_OPEN_TAB=true, every email your dev server sends opens in a browser tab rendered exactly as it would deliver. No SMTP, no sandbox accounts, no checking a real inbox.
Templates are React components in src/domain/email/templates/, rendered through React Email: auth templates (verification, reset, magic link) and marketing templates (contact notification, newsletter notification). Edit them like any component; the preview workflow gives immediate feedback.
A lightweight in-memory rate limiter (src/domain/email/rate-limit.ts) guards send paths from abuse. It is keyed and windowed, and the same utility is reused by other abuse-sensitive endpoints like AI chat.