Files
anwar-pi-website/docs/adr/0003-cloudflare-email-rest-not-smtp.md
T
AHMED 165efc3269 Docs: new README (Arabic flows, screenshots, architecture) + AGENT.md for AI agents
- README rewritten for the real Next.js + Payload + Postgres stack
  (old one described the dead static prototype)
- Logo + 4 live screenshots (home, articles, post, subscription)
- Arabic flow explanations with mermaid diagrams: visitor journey,
  content pipeline, request path
- Architecture table, quick start, API reference, security summary,
  deploy commands
- AGENT.md: orientation for future AI agents (data model, trust rules,
  rate limits, routes, env, deploy, house rules)
- GLOSSARY.md and docs/adr committed (linked from README)
2026-10-09 17:23:14 +03:00

1.8 KiB

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.