# The Cloudflare Email Routing alternative for AI agents

> Source: <https://mailkite.dev/blog/cloudflare-email-routing-for-ai-agents/>
> Published: 2026-07-04 00:00:00+00:00

# The Cloudflare Email Routing alternative for AI agents

Cloudflare Email Routing is free inbound forwarding and Email Workers hand your agent the raw MIME stream: great plumbing if you're all-in on Cloudflare and happy to parse MIME, derive trust, and wire the reply constraints yourself. MailKite (which we build) hands an agent parsed JSON with an auth verdict and a receive→reply loop in a few lines. For anyone giving an autonomous agent its own inbox.

Here’s that split in one picture: the same inbound email landing at an autonomous agent, and everything each side makes you operate between the SMTP edge and the model loop. The rest of this post is the honest version of the diagram, where Email Routing genuinely wins for an agent, the real Cloudflare-native code, and the handful of lines that are the whole MailKite side.

Here’s the whole receive → think → reply loop on MailKite. It runs as pasted on Node 18+ (`npm install express mailkite`

), and it’s the entire agent-side integration:

``` python
import express from "express";
import { MailKite } from "mailkite";

const app = express();
const mk = new MailKite(process.env.MAILKITE_API_KEY);
const SECRET = process.env.MAILKITE_WEBHOOK_SECRET;

app.post("/hooks/agent", express.raw({ type: "application/json" }), async (req, res) => {
  if (!MailKite.verifyWebhook(req.headers["x-mailkite-signature"], req.body, SECRET)) {
    return res.sendStatus(401);            // signature + replay window, one call
  }
  res.sendStatus(200);                     // ack fast; run the agent out of band

  const event = JSON.parse(req.body);
  if (event.type !== "email.received") return;

  // The body is untrusted INPUT, never instructions. Weight it by the auth verdict.
  const answer = await runAgent({
    task: event.text,
    from: event.from.address,
    trusted: event.auth.spf === "pass" && event.auth.dmarc === "pass",
  });

  await mk.send({
    from: event.to[0].address,             // reply from the address it was sent to
    to: event.from.address,                // to anyone, not just verified addresses
    subject: `Re: ${event.subject}`,
    inReplyTo: event.id,                   // threads the reply
    html: answer.html,
  });
});

app.listen(3000);
```

That’s a fully autonomous email agent: it hears, it thinks, it answers. The same handler shape exists for Python, Ruby, Go, PHP, and Java; see the [receiving docs](/docs/receiving) and [sending docs](/docs/sending). The companion repo is [ demo-cloudflare-email-routing-ai-agent](https://github.com/mailkite/demo-cloudflare-email-routing-ai-agent), and you can

[open it in StackBlitz](https://stackblitz.com/github/mailkite/demo-cloudflare-email-routing-ai-agent?file=server.mjs)(real Node in a browser tab) to fire the exact signed payload our delivery worker sends and watch a parsed email land.

## Where Cloudflare wins for agents, honestly

Email Routing is a genuinely good inbound primitive, and free at the point where most alternatives start metering:

If you want to own the whole edge and you’re happy writing the parsing, trust, and reply plumbing yourself, Cloudflare is a legitimate choice. We build MailKite on Cloudflare, for exactly these reasons. The rest of this post is about the plumbing between “an Email Worker fired” and “my agent answered safely.”

## What Cloudflare asks of an agent builder

The `email()`

handler hands you `message.raw`

: a `ReadableStream`

of raw MIME. There is no parsed body, and no auth verdict object. You run a MIME parser (Cloudflare documents [postal-mime](https://developers.cloudflare.com/email-routing/email-workers/runtime-api/)), you derive trust yourself, and you construct the reply by hand under a set of constraints. Here’s the honest version, top to bottom:

Here’s the Cloudflare version of the loop up top: a real `email()`

handler that parses the raw MIME and replies ([ cloudflare-worker/index.mjs](https://github.com/mailkite/demo-cloudflare-email-routing-ai-agent/blob/main/cloudflare-worker/index.mjs) in the demo repo).

```
// Cloudflare Email Worker: raw MIME in, you parse, you build the reply by hand.
import PostalMime from "postal-mime";
import { EmailMessage } from "cloudflare:email";
import { createMimeMessage } from "mimetext";

export default {
  async email(message, env, ctx) {
    // No parsed fields, no auth verdict — message.raw is a ReadableStream of MIME.
    const email = await PostalMime.parse(message.raw);
    const from = message.from;            // envelope sender, trivially spoofable
    const text = email.text ?? "";        // you decoded this yourself

    // There's no auth block: derive trust from the headers/DMARC before acting.
    const answer = await runAgent({ task: text, from });

    // reply() is constrained: DMARC must pass, once per message, and the
    // recipient must equal the original sender. To email anyone else you'd
    // move to the separate Email Sending product (beta).
    const msg = createMimeMessage();
    msg.setHeader("In-Reply-To", message.headers.get("Message-ID"));
    msg.setSender({ addr: message.to });
    msg.setRecipient(message.from);
    msg.setSubject("Re: " + message.headers.get("Subject"));
    msg.addMessage({ contentType: "text/html", data: answer.html });
    await message.reply(new EmailMessage(message.to, message.from, msg.asRaw()));
  },
};
```

None of this is exotic if you already live in Workers. But four things are on you that a purpose-built inbound platform hands you decoded: the MIME parse, the trust decision (there’s no `auth`

object, so an agent following a spoofed `From:`

is your problem to prevent), the by-hand reply construction, and the recipient constraint. Email Routing’s `send_email`

binding only reaches verified destination addresses; free-form “send to any recipient” is the newer [Email Sending](https://developers.cloudflare.com/email-service/get-started/send-emails/) product, in public beta as of April 2026, which needs a sending-verified domain. Cloudflare’s [Agents SDK](https://developers.cloudflare.com/agents/communication-channels/email/) adds `onEmail()`

and `replyToEmail()`

helpers on top, but they don’t parse the MIME or hand you an auth verdict either. You still build the loop.

## The comparison, agent-relevant rows

| Cloudflare Email Routing | MailKite | |
|---|---|---|
| Inbound to the agent | Raw MIME stream in an Email Worker (parse yourself) | One parsed JSON webhook |
| Auth verdict | None — derive SPF/DKIM/DMARC yourself | `auth{spf,dkim,dmarc,spam}` in the payload |
| Reply to the sender | `reply()` , DMARC-gated, once, sender-only | `mk.send()` , threaded via `inReplyTo` |
| Send to any recipient | Separate Email Sending product (beta), sending-verified domain | Included: DNS-verify, then send to anyone |
| Attachments | Decode from MIME yourself | Signed URLs in the payload |
| DNS requirement | Domain’s DNS must be on Cloudflare | Any DNS host; add MX + SPF + DKIM |
| Managed agent loop | None (build it in the Worker) | Optional route `action: 'agent'` on a queue |
| Free inbound | Yes, forwarding is free | Free tier: 3,000 msgs/mo, in + out |

The through-line: Cloudflare gives you a free, low-latency inbound primitive and the raw material to do anything with it. MailKite gives an agent the message already decoded, already authenticated, with a reply path that isn’t scoped to verified recipients.

## What actually hits your agent’s webhook

The same inbound email, delivered parsed. No `message.raw`

stream, no postal-mime, and the `auth`

block means the agent never re-derives SPF/DKIM/DMARC to decide whether to trust a sender:

```
{
  "id": "msg_2Hk9…",
  "type": "email.received",
  "from": { "address": "ada@example.com" },
  "to": [{ "address": "agent@myapp.ai" }],
  "subject": "Re: invoice #1042",
  "text": "Looks good — approved!",
  "html": "<p>Looks good — approved!</p>",
  "threadId": "<a1b2c3@mail.example.com>",
  "auth": { "spf": "pass", "dkim": "pass", "dmarc": "pass", "spam": "ham" },
  "attachments": [
    { "id": "msg_2Hk9…:0", "filename": "po.pdf", "contentType": "application/pdf",
      "size": 18213, "url": "https://api.mailkite.dev/att/2Hk9…/0?exp=…&sig=…" }
  ]
}
```

Treat the body as untrusted input, always. `From:`

is plain text and trivially forged, so an email body is a prompt-injection vector the moment an agent *follows* it. The `auth`

block is there so the agent can check whether SPF and DKIM passed before it weights a sender’s instructions; that check is necessary, not sufficient. Bound the agent’s authority regardless. See [webhook security](/docs/webhook-security) and the [agent security post](/blog/agent-inbox-security-by-design/).

## One MailKite arc

MailKite, which we build, is the layer we put on top of the exact Cloudflare primitives above: an MX edge that parses and authenticates, a signed and retried JSON webhook with the `auth`

verdict, and a Send API that replies to anyone with `inReplyTo`

threading. If you’d rather not host the model loop at all, point a route’s `action`

at `agent`

with a free-text `agentPrompt`

, and MailKite runs the turns for you on a Cloudflare Queue with a per-run transcript, capped and reaped so a slow model call never wedges the pipeline. Bring your own loop, or hand it over; same parsed inbound edge either way. Details in the [receiving docs](/docs/receiving) and [quickstart](/docs/quickstart).

## FAQ

**Can Cloudflare Email Routing reply to emails?**
Yes, as of March 2025. An Email Worker can call `message.reply()`

, and a `send_email`

binding can send to verified destination addresses for free. The reply is constrained: the incoming message must pass DMARC, a message can be replied to only once, and the recipient must match the original sender. To email an arbitrary recipient you move to Cloudflare’s separate Email Sending product (public beta, April 2026), which needs a sending-verified domain. MailKite replies to anyone with one `mk.send()`

call once your domain passes SPF + DKIM.

**Can an AI agent read email with Cloudflare Email Workers?**
Yes, but you do the decoding. The `email()`

handler gives you `message.raw`

, a raw MIME stream; you run a parser (Cloudflare documents postal-mime) to get subject, body, and attachments. There’s no normalized JSON and no SPF/DKIM/DMARC verdict object. MailKite delivers the message already parsed to JSON with an `auth`

block, so the agent reads plain fields.

**Does Cloudflare Email Routing require my DNS on Cloudflare?**
Yes. Email Routing needs the domain’s DNS managed by Cloudflare (nameservers pointed at Cloudflare); it then auto-adds the MX and SPF records. MailKite works with any DNS host: you add MX to receive and SPF + DKIM to send, wherever your DNS lives.

**Can Cloudflare send email to any address for an agent?**
Not through Email Routing alone. The routing `send_email`

binding is scoped to verified destination addresses, and `reply()`

only answers the original sender. Sending to arbitrary recipients requires the newer Email Sending product (beta) with a sending-verified domain. On MailKite, DNS-verify the domain and you can send to anyone, no verified-recipient list.

**How is MailKite different from Cloudflare Email Workers for an agent?**
Cloudflare hands your Worker the raw MIME and the reply constraints; you build the parse, the trust decision, and the reply plumbing. MailKite hands the agent parsed JSON with an `auth`

verdict, a resolved `threadId`

, attachments as signed URLs, and a `send()`

that replies to anyone, plus an optional `action: 'agent'`

route that runs the loop for you. It’s the assembled version of the Cloudflare stack.

If Cloudflare has your agent parsing raw MIME in a Worker and juggling reply constraints just to answer one email, there’s a simpler shape. Clone the [demo repo](https://github.com/mailkite/demo-cloudflare-email-routing-ai-agent) (or [run it in your browser](https://stackblitz.com/github/mailkite/demo-cloudflare-email-routing-ai-agent?file=server.mjs)), then [point a domain at MailKite](/docs/quickstart) and your agent’s next inbound email arrives as parsed JSON with an auth verdict attached.

*Related: the pillar on giving your AI agent its own inbox, agent inbox security by design, the full MailKite vs Cloudflare Email Routing comparison, and why Cloudflare Email Routing can’t reply (and how to send from your domain).*
