Cloudflare Email Service
The apps/api ships Cloudflare Email Service as the default outbound mail provider. It’s bundled into Workers Paid with no documented per-message charge, making it usually the cheapest transactional path at non-trivial volume among the providers wired into this template.
Why it’s the default
Section titled “Why it’s the default”| Provider | Cost Dynamics | Setup Path | Preconditions |
|---|---|---|---|
| Cloudflare Email | Workers Paid bundle ($5/mo flat; zero per-message cost) | Auto-provisioned SPF/DKIM/DMARC | DNS zone must be managed on Cloudflare |
| Resend | Free tier, then pay-per-message usage | Manual DNS TXT records validation | None (vendor-neutral API key) |
| SendGrid | Free tier, then pay-per-message usage | Manual DNS TXT records validation | None (vendor-neutral API key) |
Pricing tiers, free-tier limits, and per-message rates evolve. See Cloudflare, Resend, and SendGrid for current numbers.
If you’re already paying for Workers, Cloudflare Email has no per-message cost. BoringStack defaults to it because the marginal cost is zero.
Integration
Section titled “Integration”The API app’s email layer is pluggable: one IEmailService interface, five concrete implementations (Cloudflare, Resend, SendGrid, SMTP, noop). The Cloudflare provider POSTs to an account-scoped endpoint on api.cloudflare.com with a bearer token. Retries, structured logging, and the shared retryWithBackoff wrapper apply equally to all providers; Cloudflare gets the same reliability treatment as Resend. For local template iteration, set EMAIL_PROVIDER=smtp with SMTP_HOST=mailpit and the Mailpit overlay catches everything for inspection.
See Email for the abstraction shape and how dispatch works.
Dev quickstart
Section titled “Dev quickstart”By default, dev with no Cloudflare keys uses the noop provider, which logs the rendered payload to stdout instead of sending.
To actually send from dev, inject Cloudflare credentials and a verified sender into compose/.env:
echo 'EMAIL_PROVIDER=cloudflare' >> compose/.envecho 'EMAIL_FROM=noreply@yourdomain.com' >> compose/.envecho 'CLOUDFLARE_ACCOUNT_ID=your_account_id' >> compose/.envecho 'CLOUDFLARE_EMAIL_API_TOKEN=your_scoped_api_token' >> compose/.envProduction setup
Section titled “Production setup”The step-by-step walkthrough lives in the Cloudflare Email setup runbook: enable Workers Paid, enable Email Service on your domain, capture the account ID, scope a token, smoke-test.
Switching to Resend or SendGrid
Section titled “Switching to Resend or SendGrid”Change env vars to switch providers. For Resend:
echo 'EMAIL_PROVIDER=resend' >> compose/.envecho 'RESEND_API_KEY=re_your_resend_api_key' >> compose/.envFor SendGrid:
echo 'EMAIL_PROVIDER=sendgrid' >> compose/.envecho 'SENDGRID_API_KEY=SG.your_sendgrid_key' >> compose/.envThe env validator refuses to boot in production if the matching key is missing. Switching providers is a one-redeploy operation, not a code change.
Caveats
Section titled “Caveats”- Cloudflare Email Service is in beta. Pricing may evolve; daily limits are variable and account-scoped.
- DNS must be on Cloudflare. Auto-provisioned SPF/DKIM/DMARC are how deliverability works; you can’t skip the step.
- New accounts can only send to verified addresses until upgraded. The runbook covers the unlock.
- No outbound webhook for bounce / complaint events. Cloudflare maintains its own suppression list and the send API rejects suppressed recipients directly; the API template mirrors that verdict into a local table so the next send short-circuits without the round-trip. Historical event detail lives in Cloudflare’s GraphQL Analytics API (
emailSendingAdaptive) and the Email Logs dashboard. See Bounce handling for the per-provider model.
Related
Section titled “Related”- Cloudflare Email setup; the end-to-end walkthrough.
- Email; the pluggable provider abstraction.
- Email in development; Mailpit when you want to iterate without sending.
- Env validator; the boot-time check that pairs with provider config.