---
title: "Email"
description: "Maizzle templates, inline vs Cloudflare Queue delivery, and the console sender in development."
canonical_url: "https://vinicunca.dev/taman/guides/email"
---
# Email

> Maizzle templates, inline vs Cloudflare Queue delivery, and the console sender in development.

Transactional mail is rendered by `@taman/emails` and sent from `apps/api/server/email`.

## Templates

Maizzle project under `packages/server/emails`. Scripts:

```bash
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` → queue `taman-email` (renamed to `<name>-email` on generate)
- Consumer: `max_retries: 5`, DLQ `taman-email-dlq`
- Plugin `server/plugins/email-queue.ts` handles `cloudflare:queue` when `batch.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>-email` and `<name>-email-dlq` in the Cloudflare account (names must match wrangler output after generate)
- Enable Email Routing / `send_email` for the Worker
- Set `NITRO_EMAIL_FROM` to a domain you control
- Confirm the consumer Worker is the same deployment as the API (`deployConfig: true` on Nitro)

Exact Cloudflare dashboard clicks are **not** verified in-repo; derive names from `nitro.config.ts` and `template.manifest.ts`.


## Sitemap

See the full [sitemap](https://vinicunca.dev/sitemap.md) for all pages.
