Paste this into Claude Code (or Cursor, or any agent) running inside your project.
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.
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.
Before writing any code, ask me to confirm:
email_sending:write scope (created at dash.cloudflare.com/profile/api-to… → Custom Token)Don't proceed until you have these.
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 headersEach subdomain needs to be onboarded separately in Cloudflare. Ask me which I want.
Use grep/search to find every place in this codebase that sends email. Look for:
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.
Don't sprinkle Cloudflare API calls across the codebase. Add one helper (provider-specific name like sendEmailViaCloudflare()) that:
from from a config var (don't hardcode)"Name <email@domain>" strings into the API's {address, name} object formcc/bcc as either string or arrayheaders dict (newsletters need List-Unsubscribe + List-Unsubscribe-Post)bool (true on success, false on any failure)permanent_bounces: [...] non-empty as a soft failureDon'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.
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.
After each successful migration, suggest a focused git commit with a clear message. Don't bundle unrelated changes.
permanent_bounces array.messageId for tracking if I need it.headers.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.