Transactional mail is rendered by @taman/emails and sent from apps/api/server/email.
Templates
Maizzle project under packages/server/emails. Scripts:
pnpm --filter @taman/emails build:templates
pnpm --filter @taman/emails dev:templates
Templates and params:
| Template | Trigger |
|---|---|
invitation | organization sendInvitationEmail |
verify-email | emailVerification.sendVerificationEmail |
reset-password | emailAndPassword.sendResetPassword |
Locales: en-US, id-ID. renderEmail(job) returns subject + HTML (+ plaintext from the Maizzle build). Invalid jobs throw EmailJobError.
NITRO_APP_URL is used for invitation links. From: NITRO_EMAIL_FROM / NITRO_EMAIL_FROM_NAME.
Sender and dispatcher
createEmailServices(env, config) in server/email/index.ts:
| Binding | Behavior |
|---|---|
env.EMAIL present | createCloudflareEmailSender (send_email binding named EMAIL) |
| else | createConsoleEmailSender — logs the message (local nitro dev) |
env.EMAIL_QUEUE present | enqueue via EMAIL_QUEUE producer |
| else | createInlineEmailDispatcher — deliver immediately |
The Cloudflare queue is declared in nitro.config.ts:
- Producer binding
EMAIL_QUEUE→ queuetaman-email(renamed to<name>-emailon generate) - Consumer:
max_retries: 5, DLQtaman-email-dlq - Plugin
server/plugins/email-queue.tshandlescloudflare:queuewhenbatch.queue === 'taman-email'
Poison messages: EmailJobError is acked (will not retry). Other errors retry until the DLQ.
Wiring from auth
better-auth.instance.ts dispatches jobs; it does not send SMTP itself. Locale comes from emailLocale(request).
Local development
No Cloudflare bindings → console sender + inline dispatcher. Watch the API terminal for HTML dumps. You still need valid NITRO_EMAIL_FROM for the deliverer config.
Production checklist
- Create queues
<name>-emailand<name>-email-dlqin the Cloudflare account (names must match wrangler output after generate) - Enable Email Routing /
send_emailfor the Worker - Set
NITRO_EMAIL_FROMto a domain you control - Confirm the consumer Worker is the same deployment as the API (
deployConfig: trueon Nitro)
Exact Cloudflare dashboard clicks are not verified in-repo; derive names from nitro.config.ts and template.manifest.ts.