Mail module
Transactional email providers, templates, and sending flow.
Mail module
Transactional email (verification, password reset, contact form, subscription welcome). Resend and Cloudflare Email Service are the built-in providers. Design allows adding other providers via a provider registry without changing callers.
Consumers: Auth (sendVerificationEmail, sendResetPassword) and newsletter subscribe — all use sendEmail(...) only.
Directory structure
src/mail/
├── index.ts # sendEmail, getMailProvider, getTemplate; providerRegistry
├── types.ts # EmailTemplate, MailProviderName, Send*Params, MailProvider
├── render.ts # getTemplate, renderEmailHtml, toPlainText; subjectByTemplate
├── provider/
│ ├── resend.ts # ResendProvider implements MailProvider
│ └── cloudflare.ts # CloudflareProvider implements MailProvider
├── templates/
│ ├── verify-email.tsx
│ ├── forgot-password.tsx
│ ├── subscribe-newsletter.tsx
│ └── contact-message.tsx
└── components/
├── email-layout.tsx
└── email-button.tsxConfiguration
| Source | Key | Description |
|---|---|---|
websiteConfig.mail | provider | 'resend' or 'cloudflare'. Extend in src/types/index.d.ts when adding providers. |
fromEmail | Sender address (required for sending). | |
supportEmail | Used by contact form target. | |
| Env var | RESEND_API_KEY | Required when using the Resend provider. |
| Env var | CLOUDFLARE_ACCOUNT_ID | Cloudflare account ID; required when using Cloudflare Email provider. |
| Env var | CLOUDFLARE_SERVICE_API_TOKEN | Cloudflare API token; required when using Cloudflare provider. |
Providers
Resend
Uses the Resend SDK. Requires RESEND_API_KEY env var.
// src/config/website.ts
mail: {
enable: true,
provider: 'resend',
fromEmail: 'MyApp <support@example.com>',
}Cloudflare Email Service
Uses the Cloudflare Email Service REST API. Works in any Node.js environment (Workers, CI/CD, scheduled scripts) — no Workers binding required.
Prerequisites:
- Your domain must be using Cloudflare DNS.
- Onboard your domain in the Cloudflare dashboard under Email Sending.
- Create an API token with
com.cloudflare.api.token.Email.Sendpermission in the Cloudflare dashboard.
Environment variables:
CLOUDFLARE_ACCOUNT_ID=your_account_id
CLOUDFLARE_SERVICE_API_TOKEN=your_api_tokenUsage:
// src/config/website.ts
mail: {
enable: true,
provider: 'cloudflare',
fromEmail: 'MyApp <support@example.com>',
}Note: The
fromEmailaddress must be on a verified domain in your Cloudflare account.
API
| Export | Description |
|---|---|
| sendEmail(params) | SendTemplateParams → render template + send; SendRawEmailParams → send raw. Returns Promise<SendEmailResult>. |
| getMailProvider() | Lazy-initialized provider from websiteConfig.mail.provider. |
| getTemplate({ template, context }) | Returns { html, text, subject }; used by providers internally. |
Types (re-exported): EmailTemplate, MailProviderName, SendTemplateParams, SendRawEmailParams.
Templates
| Template | Context | Subject (in render.ts) |
|---|---|---|
| forgotPassword | { url, name } | Reset your password |
| verifyEmail | { url, name } | Verify your email |
| subscribeNewsletter | { email? } | Thanks for subscribing |
| contactMessage | { name, email, message } | Contact Message from Website |
Adding a template: extend EmailTemplate in types.ts → add to EmailTemplates and subjectByTemplate in render.ts → add React component under templates/.
Adding a new mail provider
The module uses a provider registry (providerRegistry in index.ts). To add a new provider:
- Types — In
src/types/index.d.ts, extendMailConfig.providerunion (e.g.'resend' | 'cloudflare' | 'newprovider'). - Implementation — Add
src/mail/provider/<name>.tsimplementingMailProvider(sendTemplate,sendRawEmail,getProviderName). UsegetTemplatefrom../renderfor template-based sends. - Registration — In
src/mail/index.ts, add a factory toproviderRegistry:newprovider: () => new NewProvider(...), reading provider-specific env/bindings inside.
Callers continue using sendEmail(...) only.
Dependencies
- resend — Resend SDK (when using Resend provider).
- React / react-dom/server — Template rendering (
renderToReadableStreamorrenderToStaticMarkup).