Generated banner for Cloudflare Email Service
Generated logo for Cloudflare Email Service

Cloudflare Email Service

CloudflareOfficialPublished by Cloudflare

  • Skill
  • Tool integration
  • Cloud and DevOps
  • Apache-2.0
  • No issues found

From the author

Implement or troubleshoot Cloudflare Email Sending and Email Routing integrations and their delivery configuration.
— the description by Cloudflare

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

The Cloudflare Email Service skill provides a framework for implementing transactional email sending and inbound email routing within the Cloudflare ecosystem. It covers domain onboarding, configuration of Workers bindings, REST API usage, and best practices for maintaining high deliverability. The skill emphasizes using retrieval-based methods to ensure developers access the most current API specifications and platform limits, as the service is rapidly evolving.

What it does

  • Enables transactional email sending via Cloudflare Workers or REST API.
  • Configures inbound email routing and processing using Workers handlers.
  • Provides commands for domain onboarding and DNS record management.
  • Offers guidance on deliverability, including SPF, DKIM, and DMARC configuration.
  • Supports integration with Cloudflare Agents SDK for automated email workflows.
  • Facilitates monitoring of sending limits, bounce handling, and suppression lists.

When to use it

  • When you need to send transactional emails from a Cloudflare Worker or external application.
  • When you need to receive and process incoming emails programmatically.
  • When setting up a new domain for email services on the Cloudflare platform.
  • When troubleshooting email delivery issues or managing suppression lists.
  • When integrating email capabilities into an AI agent built with the Cloudflare Agents SDK.

Advantages

  • Native integration with Cloudflare Workers and infrastructure.
  • Automatic handling of SPF, DKIM, and bounce management.
  • Unified platform for both sending and routing email.
  • Direct access to analytics and delivery feedback via API.
  • Supports both programmatic (API/Worker) and CLI-based management.

Usage examples

“How do I enable email sending for my domain example.com using the CLI?”
The agent will provide the command 'npx wrangler email sending enable example.com' and explain how to verify DNS records.
“Show me how to send a transactional email from a Cloudflare Worker.”
The agent will guide you to configure the 'send_email' binding in wrangler.jsonc and use the appropriate Worker binding syntax.
“How can I check my daily email sending quota?”
The agent will provide the REST API endpoint for the limits resource and explain how to authenticate using your API token.
“What is the best way to handle incoming emails in a Worker?”
The agent will explain the use of the email() handler and provide a pattern for parsing raw email content.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/cloudflare/cloudflare-email-service/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip cloudflare-email-service.zip -d .agents/skills

Documentation by Cloudflare

Cloudflare Email Service

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.

Retrieval Sources

SourceHow to retrieveUse for
Cloudflare docsCloudflare MCP docs tool or URL https://developers.cloudflare.com/email-service/index.mdAPI reference, limits, pricing, latest features
REST API spechttps://developers.cloudflare.com/api/resources/email_sending/index.mdOpenAPI spec for the Email Sending REST API
Workers typeshttps://www.npmjs.com/package/@cloudflare/workers-typesType signatures, binding shapes
Agents SDK docsEmail agent walkthroughEmail handling in Agents SDK

FIRST: Check Prerequisites

Before writing any email code, verify the basics are in place:

  1. Domain onboarded? Run 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.

  2. Binding configured? Look for send_email in wrangler.jsonc (for Workers)

  3. postal-mime installed? Run npm ls postal-mime (only needed for receiving/parsing emails)

What Do You Need?

Start here. Find your situation, then follow the link for full details.

I want to...PathReference
Send emails from a Cloudflare WorkerWorkers binding (no API keys needed)sending.md
Send emails from an AI agent built with Cloudflare Agents SDKonEmail() + replyToEmail() in Agent classsending.md
Send emails from an external app or agent (Node.js, Go, Python, etc.)REST API with Bearer tokenrest-api.md
Send emails from a coding agent (Claude Code, Cursor, Copilot, etc.)MCP tools, wrangler CLI, or REST APIcli-and-mcp.md
Receive and process incoming emails (Email Routing)Workers email() handlerrouting.md
Set up Email Sending or Email Routingwrangler email sending enable / wrangler email routing enable, or Dashboardcli-and-mcp.md
Improve deliverability, avoid spam foldersAuthentication, content, compliancedeliverability.md

Sending Workflow

Prefer the binding for Workers; use REST for external apps or when explicitly requested. Read sending.md or rest-api.md to retrieve the documentation for the selected task before writing code. These guides cover setup, recipients, attachments, headers, limits, response handling, and errors; the Workers guide also covers Agents SDK integration and types matched to the project configuration.

Common Mistakes

MistakeWhy It HappensFix
Forgetting send_email binding in wrangler configEmail Service uses a binding, not an API keyAdd "send_email": [{ "name": "EMAIL" }] to wrangler.jsonc
Sending from an unverified domainDomain must be onboarded onto Email Sending before first sendRun wrangler email sending enable yourdomain.com or onboard in Dashboard
Reading 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 versions
Using email for marketing/bulk sendsEmail Service is for transactional email onlyUse a dedicated marketing email platform for newsletters and campaigns
Forwarding to unverified destinationsmessage.forward() only works with verified addressesRun wrangler email routing addresses create [email protected] or add in Dashboard
Testing with fake addressesBounces from non-existent addresses hurt sender reputationUse real addresses you control during development
Hardcoding API tokens in source codeTokens in code get committed and leakedUse environment variables or Cloudflare secrets
Ignoring the from domain requirementThe from address must use a domain onboarded to Email ServiceVerify the domain first, then send from [email protected]
Using email key in REST API from objectREST API uses address not email for from objectUse { "address": "...", "name": "..." } for REST, { "email": "...", "name": "..." } for Workers
Using replyTo in REST APIREST API uses snake_case field namesUse reply_to for REST API, replyTo for Workers binding

References

Read the reference that matches your situation. You don't need all of them.

Skill feedback

If the user reports that this skill gave incorrect or outdated guidance, or is missing information, read references/skill-feedback.md.

Files

references/routing.md

Receiving & Routing Inbound Email

Handle incoming emails sent to your domain via a Worker's email() handler. Forward, reply, reject, or parse emails programmatically.

For full API details, see the Email Routing docs.

Email Handler

Export an email() function from your Worker. No special wrangler binding needed — a routing rule connects incoming addresses to your Worker.

export default {
  async email(message, env, ctx): Promise<void> {
    console.log(`Email from ${message.from} to ${message.to}`);
    await message.forward("[email protected]");
  },
} satisfies ExportedHandler<Env>;

Set up routing rules in Dashboard > Compute & AI > Email Service > Email Routing > Routing Rules, or via wrangler email routing rules create.

ForwardableEmailMessage

The message parameter is a ForwardableEmailMessage. Run npx wrangler types to get the full type definition. Key properties and methods:

  • message.from / message.to — envelope addresses (SMTP MAIL FROM / RCPT TO). message.from is trustworthy; header addresses can be spoofed.

  • message.headers — Headers object (use .get("subject"), .get("message-id"), etc.)

  • message.raw — ReadableStream<Uint8Array> of raw MIME content. Single use — buffer before accessing.

  • message.rawSize — size in bytes

  • message.setReject(reason) — reject with a permanent SMTP error

  • message.forward(rcptTo, headers?) — forward to a verified destination

  • message.reply(emailMessage) — reply with an EmailMessage object

Core Operations

Forward
await message.forward("[email protected]");

// With custom headers
await message.forward("[email protected]", new Headers({
  "X-Original-Recipient": message.to,
}));

Destination must be verified first (Dashboard or wrangler email routing addresses create).

Reject
message.setReject("Your message was blocked");
Reply

Using env.EMAIL.send() (recommended — no extra dependencies):

async email(message, env, ctx) {
  const subject = message.headers.get("subject") || "";
  await env.EMAIL.send({
    to: message.from,
    from: message.to,
    subject: `Re: ${subject}`,
    html: "<p>Thanks! We'll respond shortly.</p>",
    text: "Thanks! We'll respond shortly.",
  });
  await message.forward("[email protected]");
}

Using message.reply() with MIME (more control, requires mimetext + nodejs_compat):

import { EmailMessage } from "cloudflare:email";
import { createMimeMessage } from "mimetext";

async email(message, env, ctx) {
  const msg = createMimeMessage();
  const messageId = message.headers.get("Message-ID");
  if (messageId) msg.setHeader("In-Reply-To", messageId);
  msg.setSender({ name: "Support", addr: "[email protected]" });
  msg.setRecipient(message.from);
  msg.setSubject("Re: " + (message.headers.get("subject") || ""));
  msg.addMessage({ contentType: "text/plain", data: "Thanks for reaching out!" });

  await message.reply(new EmailMessage("[email protected]", message.from, msg.asRaw()));
}

Parsing Emails

Use postal-mime to parse raw MIME content:

import PostalMime from "postal-mime";

async email(message, env, ctx) {
  const rawBuffer = await new Response(message.raw).arrayBuffer();
  const parsed = await PostalMime.parse(rawBuffer);

  console.log("Subject:", parsed.subject);
  console.log("Text:", parsed.text);
  console.log("Attachments:", parsed.attachments.length);
}

Store and Reply Later (Human-in-the-Loop)

A common pattern is to store incoming emails in a Durable Object (SQLite) so a human or AI agent can review and reply later — rather than replying immediately in the email() handler. This enables support inboxes, approval workflows, and AI-drafted replies.

Architecture
Email Routing → email() handler → parse + store in DO → user reviews later → reply via send_email binding

The email() handler stores the email and returns immediately. Replies happen later via a separate HTTP request or agent action.

Receive and Store
import PostalMime from "postal-mime";

export class MailboxDO extends DurableObject {
  async storeEmail(from: string, to: string, subject: string, body: string,
                   messageId: string, inReplyTo: string | null) {
    this.ctx.storage.sql.exec(
      `INSERT INTO emails (sender, recipient, subject, body, message_id, in_reply_to, date, read)
       VALUES (?, ?, ?, ?, ?, ?, datetime('now'), 0)`,
      from, to, subject, body, messageId, inReplyTo
    );
  }
}

export default {
  async email(message, env, ctx) {
    const raw = await new Response(message.raw).arrayBuffer();
    const parsed = await PostalMime.parse(raw);

    const id = env.MAILBOX.idFromName(message.to);
    const stub = env.MAILBOX.get(id);

    await stub.storeEmail(
      message.from,
      message.to,
      parsed.subject || "(no subject)",
      parsed.text || parsed.html || "",
      message.headers.get("message-id") || "",
      message.headers.get("in-reply-to") || null,
    );

    // Optionally trigger an AI agent to draft a reply (non-blocking)
    // ctx.waitUntil(notifyAgent(env, message.to, emailId));
  },
} satisfies ExportedHandler<Env>;
Reply Later

When a user (or agent) decides to reply, build proper threading headers and send via the send_email binding:

// In an HTTP handler or agent tool — not in the email() handler
async function replyToStoredEmail(env: Env, original: StoredEmail, replyBody: string) {
  // Build threading headers (In-Reply-To + References per RFC 2822)
  const headers: Record<string, string> = {};
  if (original.messageId) {
    headers["In-Reply-To"] = original.messageId;
    headers["References"] = original.messageId;
  }

  await env.EMAIL.send({
    to: original.sender,
    from: original.recipient,
    subject: `Re: ${original.subject}`,
    text: replyBody,
    html: `<p>${replyBody}</p>`,
    headers,
  });
}
Key Design Points
  • Buffer message.raw once, parse with postal-mime, then store structured fields. Don't store the raw stream.

  • Extract Message-ID, In-Reply-To, and References headers during ingest for threading. Fall back to subject-based matching for emails without threading headers.

  • Use Durable Object SQLite for per-mailbox storage — each mailbox gets its own DO instance keyed by email address, providing natural isolation.

  • Store attachments separately in R2 (binary blobs), with metadata in SQLite.

  • Defer heavy work (AI drafting, notifications) via ctx.waitUntil() so the email() handler returns quickly.

  • Never auto-send from the email() handler in a human-in-the-loop flow. Store a draft, let the user review, then send via a separate action.

Gotchas

  • message.raw is single-use. Buffer first: const raw = await new Response(message.raw).arrayBuffer()

  • Destinations must be verified. Forwarding to unverified addresses fails silently.

  • Handler must act. If your handler returns without consuming raw, forwarding, or rejecting, the email is dropped.

  • DMARC/SPF for replies. If sending replies, ensure your domain has proper SPF/DKIM records (auto-configured on domain onboarding).

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs an API key, Needs Node.js

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Published by
Cloudflare
Publisher
Cloudflare (Official)
Source
cloudflare/skills · skills/cloudflare-email-service · commit 6eb6c78
Licence
Apache License 2.0 (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Cloudflare.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download cloudflare-email-service.zip