Forgetting send_email binding in wrangler config | Email Service uses a binding, not an API key | Add "send_email": [{ "name": "EMAIL" }] to wrangler.jsonc |
| Sending from an unverified domain | Domain must be onboarded onto Email Sending before first send | Run wrangler email sending enable yourdomain.com or onboard in Dashboard |
Reading message.raw twice in email handler | The raw stream is single-use — second read returns empty | Buffer first: const raw = await new Response(message.raw).arrayBuffer() |
Missing text field (HTML only) | Some email clients only show plain text; also helps spam scores | Always include both html and text versions |
| Using email for marketing/bulk sends | Email Service is for transactional email only | Use a dedicated marketing email platform for newsletters and campaigns |
| Forwarding to unverified destinations | message.forward() only works with verified addresses | Run wrangler email routing addresses create user@gmail.com or add in Dashboard |
| Testing with fake addresses | Bounces from non-existent addresses hurt sender reputation | Use real addresses you control during development |
| Hardcoding API tokens in source code | Tokens in code get committed and leaked | Use environment variables or Cloudflare secrets |
Ignoring the from domain requirement | The from address must use a domain onboarded to Email Service | Verify the domain first, then send from anything@that-domain.com |
Using email key in REST API from object | REST API uses address not email for from object | Use { "address": "...", "name": "..." } for REST, { "email": "...", "name": "..." } for Workers |
Using replyTo in REST API | REST API uses snake_case field names | Use reply_to for REST API, replyTo for Workers binding |