Prompt: migrate outbound email to Cloudflare Email Service

A prompt (shared by @levelsio) for an agent to migrate a codebase from Postmark, SES, Resend, SendGrid or Mailgun to Cloudflare Email Service.

@levelsio on twitter

I want to migrate this codebase’s outbound email from its current provider (Postmark / SES / Resend / SendGrid / Mailgun / etc.) to Cloudflare Email Service (public beta, launched April 2026). Help me do this carefully.

Context: what Cloudflare Email Service is

A new transactional email API from Cloudflare. Endpoint:

POST https://api.cloudflare .com/client/v4/accounts/{ACCOUNT_ID}/email/sending/send
Authorization: Bearer {API_TOKEN}
Content-Type: application/json

Request body:

{
  "to": "user@example.com",                    // string OR array of strings
  "from": "no-reply@yourdomain.com",           // string OR {"address":"x@y","name":"Display"}
  "subject": "...",
  "html": "<p>...</p>",                        // optional
  "text": "...",                               // optional (one of html/text required)
  "cc": ["..."],                               // optional, array
  "bcc": ["..."],                              // optional, array
  "reply_to": "...",                           // optional, single string
  "headers": {"List-Unsubscribe": "<...>"}     // optional, e.g. for newsletters
}

Success response: HTTP 200 + {"success":true,"result":{"delivered":[],"queued":[],"permanent_bounces":[]}}.

Failure: non-200 OR success:false OR non-empty permanent_bounces. Always check all three.

Pricing: $5/mo Workers Paid plan + 3,000 emails free + $0.35 per 1k after. Roughly 5× cheaper than Postmark. No batch send endpoint — loop single sends.

Steps you should follow

1. Verify prerequisites with me

Before writing any code, ask me to confirm:

  • I have a Cloudflare Workers Paid plan ($5/mo)
  • I’ve onboarded my sender domain(s) in Cloudflare dashboard → Email → Email Sending → Onboard Domain (this auto-adds SPF/DKIM/DMARC + cf-bounce MX records)
  • I have an API token with email_sending:write scope (created at http://dash.cloudflare.com/profile/api-tokens → Custom Token)
  • I have my Cloudflare account ID

Don’t proceed until you have these.

2. Recommend a domain reputation strategy

Most apps should split senders across 2-3 subdomains so spam complaints on one don’t drag down deliverability on others:

  • mail.<domain> or members.<domain> → transactional (login, receipts, password reset, in-app notifications)
  • e.<domain> → cold/recovery (abandoned cart, win-back campaigns)
  • newsletter.<domain> → opt-in newsletters with List-Unsubscribe headers

Each subdomain needs to be onboarded separately in Cloudflare. Ask me which I want.

3. Audit existing email sends

Use grep/search to find every place in this codebase that sends email. Look for:

  • The current provider’s SDK class names, API URLs, env/config vars
  • Generic patterns like mail(), SMTP usage, nodemailer, etc.

Group findings by email type/purpose (e.g. “magic-link login”, “payment receipt”, “weekly newsletter”) rather than by file. Tell me what you found before changing anything.

4. Add a single helper function

Don’t sprinkle Cloudflare API calls across the codebase. Add one helper (provider-specific name like sendEmailViaCloudflare()) that:

  • Defaults from from a config var (don’t hardcode)
  • Parses "Name <email@domain>" strings into the API’s {address, name} object form
  • Accepts cc/bcc as either string or array
  • Accepts a headers dict (newsletters need List-Unsubscribe + List-Unsubscribe-Post)
  • Returns bool (true on success, false on any failure)
  • On failure, logs/alerts somewhere I can see (Telegram, Sentry, log file — match what the codebase already does)
  • Sets curl/fetch timeouts (5s connect, 15s total) so a stuck CF API can’t hang the request
  • Treats permanent_bounces: [...] non-empty as a soft failure

5. Migrate one low-stakes email type first

Don’t migrate everything at once. Pick the lowest-stakes email type in the audit (something where landing in spam wouldn’t lose me money or users — e.g. “internal admin alert”, “profile photo rejection”) and migrate just that one. Test it end-to-end. Confirm the email actually arrives. Only then propose the next migration.

6. Stop me from migrating login email yet

If my codebase sends magic-link login or password-reset emails, do not migrate those to Cloudflare yet. Cloudflare Email Service is brand new (~1 month old at writing). Its IP/domain reputation is unproven. Login emails landing in spam = users locked out. Keep those on the current provider until at least 3 months of clean deliverability data on the lower-stakes types. Tell me this explicitly.

7. Suggest commit boundaries

After each successful migration, suggest a focused git commit with a clear message. Don’t bundle unrelated changes.

Important caveats to surface to me

  • Beta product. Pricing isn’t fully finalized. SLA undefined. Could change.
  • No batch endpoint. Mass sends (newsletters to 1000+ recipients) need a loop — at ~150ms/send that’s ~2.5min per 1000. Fine for crons, bad for sync user-facing flows.
  • No bounce webhooks yet. Surface failures via the response body’s permanent_bounces array.
  • Suppression list auto-managed. Hard bounces, repeated soft bounces, and spam complaints get blocked. Spam-complaint suppressions are hard to remove (anti-abuse).
  • No per-message logs/dashboard yet. Use the response’s messageId for tracking if I need it.
  • List-Unsubscribe headers are passed through verbatim — Gmail’s bulk-sender requirement still met, but only if I include them in headers.

Your first action

Before writing any code: do step 1 (ask for prerequisites) and step 3 (audit existing sends), then propose the migration order with a brief explanation of the reasoning. Wait for my confirmation before making changes.

Similar in the index

  • Disposable Email Checker API Free API that checks whether an email or domain is a disposable provider.
  • Mailflare Send, receive and organize email for your own domains, deployable on Cloudflare.
  • Sessy Self-hosted dashboard for AWS SES deliveries, bounces and complaints.
  • Doota A self-run email app where conversations read like a chat (in development).
  • Magic links: use an interstitial confirm page Email scanners pre-fetch magic links and burn the token; add an interstitial page.