# ADR 0003 — Cloudflare Email Sending REST, not SMTP Date: 2026-10-03 ## Status Accepted ## Context The site needs exactly one transactional email: the verification mail sent by `POST /api/auth/resend-verification` via `sendVerificationEmail(to, token)` in `src/lib/mail.ts`. The first implementation used nodemailer over generic SMTP. Cloudflare Email Service (the mail provider chosen for anwarpy.ai) has **no SMTP interface** — it exposes a REST endpoint only — so the nodemailer path could never be activated on the target host. ## Decision - `src/lib/mail.ts` sends via `POST https://api.cloudflare.com/client/v4/accounts/{CLOUDFLARE_ACCOUNT_ID}/email/sending/send` using `fetch`, authenticated with `Authorization: Bearer ${CLOUDFLARE_API_TOKEN}`. - The `from` field uses the REST shape `{ address, name }` (key is `address`, **not** `email`); default address `noreply@anwarpy.ai` (override: `MAIL_FROM`). - Active only when `CLOUDFLARE_API_TOKEN` **and** `CLOUDFLARE_ACCOUNT_ID` are set; otherwise `{ sent: false }` (existing degrade-gracefully contract — the resend route answers 503 with a clear message). - Non-OK response or fetch throw: `console.error` the error body, return `{ sent: false }`. No retry logic (single-shot; the resend route re-invokes). - nodemailer is uninstalled; the exported signature `sendVerificationEmail(to: string, token: string): Promise<{ sent: boolean }>` is unchanged. ## Consequences - The API token needs the Email Sending permission; the sender domain must be onboarded in Cloudflare (SPF/DKIM records created via `wrangler email sending dns get`). - `smtpConfigured()` and all `SMTP_*` env vars are gone; anything expecting them must be updated when mail features grow. - Adding more email types later means extending this one module — it is the single mail boundary.