Email Overview
What the mail module does, the two addresses, and picking a provider
Last updated November 3, 2026
The mail module gives the site three ways to send email: verification and welcome messages, contact form submissions forwarded to your inbox, and manual sends from the admin area. There are two providers — Resend or Cloudflare Email — each with its own setup page.
Three decisions
| Decision | Where | What it does |
|---|---|---|
| Sending address | MAIL_FROM | The sender of verification, password reset, and welcome emails |
| Support inbox | mail.supportEmail | Where contact form messages go; also the footer's mailto: link |
| Switch | mail.enabled | When off, mail APIs return 404 MODULE_DISABLED |
The first two are independent: setting one never fills in the other, and you can pick different addresses for them. MAIL_FROM must be a plain email address (noreply@<your-domain.com>); Brand <noreply@...> is rejected.
Picking a provider
| Provider | Required config | Setup page |
|---|---|---|
resend | RESEND_API_KEY + MAIL_FROM | Resend |
cloudflare | MAIL_FROM (or CLOUDFLARE_EMAIL_SENDER) + the EMAIL binding | Cloudflare Email |
What you get once it is on
- Auth email: with
auth.requireEmailVerificationattrue(the default), sign-up sends a
verification email, successful verification sends the welcome email, and password reset sends the reset link. When mail is not fully configured, auth is flagged as a conflict and /api/auth/* returns 503 — verification is never silently turned off.
- Contact form: with
contact.enabledattrue, submissions are sent to
mail.supportEmail. It depends on the mail module being fully configured; otherwise /api/contact returns 503 rather than telling the visitor the message was delivered.
- Manual sends:
/api/emailsends a template email from the admin area. It requires a
session and the recipient must be in the ADMIN_EMAILS allowlist.
Verify
pnpm run check:env lists what is missing for each enabled module; mail diagnostics show up under mail.
pnpm email:devPreviews the templates in src/mail/templates/ on port 3001, with hot reload.
Notes
- Only one provider is live at a time —
mail.providerpicks the path. - Without an API key or a sending address the provider is not registered, so calls fail loudly
instead of sending from a placeholder address.
- Subjects and bodies are rendered in the visitor's language; a new template needs both Chinese
and English copy.
Once mail works, Newsletter reuses the same credentials to collect subscribers.