Your knowledge of the Cloudflare Email Service, Email Routing or Email Sending may be outdated. Prefer retrieval over pre-training for any Cloudflare Email Service task.
Cloudflare Email Service lets you send transactional emails and route incoming emails, all within the Cloudflare platform. Your knowledge of this product may be outdated — it launched in 2025 and is evolving rapidly. Prefer retrieval over pre-training for any Email Service task.
If there is any discrepancy between this skill and the sources below, always trust the original source. The Cloudflare docs, REST API spec, @cloudflare/workers-types, and Agents SDK repo are the source of truth. This skill is a convenience guide — it may lag behind the latest changes. When in doubt, retrieve from the sources below and use what they say.
SourceHow to retrieveUse forCloudflare docscloudflare-docs search tool or URL https://developers.cloudflare.com/email-service/API reference, limits, pricing, latest featuresREST API spechttps://developers.cloudflare.com/api/resources/email_sendingOpenAPI spec for the Email Sending REST APIWorkers typeshttps://www.npmjs.com/package/@cloudflare/workers-typesType signatures, binding shapesAgents SDK docsFetch docs/email.md from https://github.com/cloudflare/agents/tree/main/docsEmail handling in Agents SDK
Before writing any email code, verify the basics are in place:
npx wrangler email sending list to see which domains have email sending enabled. If the domain isn't listed, run npx wrangler email sending enable userdomain.com or see cli-and-mcp.md for full setup instructions.send_email in wrangler.jsonc (for Workers)npm ls postal-mime (only needed for receiving/parsing emails)Start here. Find your situation, then follow the link for full details.
I want to...PathReferenceSend emails from a Cloudflare WorkerWorkers binding (no API keys needed)sending.mdSend emails from an AI agent built with Cloudflare Agents SDKonEmail() + replyToEmail() in Agent classsending.mdSend emails from an external app or agent (Node.js, Go, Python, etc.)REST API with Bearer tokenrest-api.mdSend emails from a coding agent (Claude Code, Cursor, Copilot, etc.)MCP tools, wrangler CLI, or REST APIcli-and-mcp.mdReceive and process incoming emails (Email Routing)Workers email() handlerrouting.mdSet up Email Sending or Email Routingwrangler email sending enable / wrangler email routing enable, or Dashboardcli-and-mcp.mdImprove deliverability, avoid spam foldersAuthentication, content, compliancedeliverability.md
Add the binding to wrangler.jsonc, then call env.EMAIL.send(). The from domain must be onboarded via npx wrangler email sending enable yourdomain.com.
jsonc
// wrangler.jsonc
{ "send_email": [{ "name": "EMAIL" }] }
typescript
const response = await env.EMAIL.send({
to: "user@example.com",
from: { email: "welcome@yourdomain.com", name: "My App" },
subject: "Welcome!",
html: "<h1>Welcome!</h1>",
text: "Welcome!",
});
The binding is recommended for Workers — no API keys needed. If a user specifically requests the REST API from within a Worker (e.g., they already have an API token workflow), that works too — see rest-api.md.
See sending.md for the full API, batch sends, attachments, custom headers, restricted bindings, and Agents SDK integration.
For apps outside Workers, or within Workers if the user explicitly requests it. Key differences from the Workers binding:
POST https://api.cloudflare.com/client/v4/accounts/{account_id}/email/sending/sendfrom object uses address (not email): { "address": "...", "name": "..." }replyTo is reply_to (snake_case){ delivered: [], permanent_bounces: [], queued: [] } (not messageId)See rest-api.md for curl examples, response format, and error handling.
MistakeWhy It HappensFixForgetting send_email binding in wrangler configEmail Service uses a binding, not an API keyAdd "send_email": [{ "name": "EMAIL" }] to wrangler.jsoncSending from an unverified domainDomain must be onboarded onto Email Sending before first sendRun wrangler email sending enable yourdomain.com or onboard in DashboardReading message.raw twice in email handlerThe raw stream is single-use — second read returns emptyBuffer first: const raw = await new Response(message.raw).arrayBuffer()Missing text field (HTML only)Some email clients only show plain text; also helps spam scoresAlways include both html and text versionsUsing email for marketing/bulk sendsEmail Service is for transactional email onlyUse a dedicated marketing email platform for newsletters and campaignsForwarding to unverified destinationsmessage.forward() only works with verified addressesRun wrangler email routing addresses create user@gmail.com or add in DashboardTesting with fake addressesBounces from non-existent addresses hurt sender reputationUse real addresses you control during developmentHardcoding API tokens in source codeTokens in code get committed and leakedUse environment variables or Cloudflare secretsIgnoring the from domain requirementThe from address must use a domain onboarded to Email ServiceVerify the domain first, then send from anything@that-domain.comUsing email key in REST API from objectREST API uses address not email for from objectUse { "address": "...", "name": "..." } for REST, { "email": "...", "name": "..." } for WorkersUsing replyTo in REST APIREST API uses snake_case field namesUse reply_to for REST API, replyTo for Workers binding
Read the reference that matches your situation. You don't need all of them.
email() handler, forwarding, replying, parsing. For receiving emails.