The Cloudflare Email Routing alternative for AI agents MailKite offers a simpler alternative to Cloudflare Email Routing for AI agents, providing parsed JSON with auth verdicts and a receive-reply loop in a few lines of code, while Cloudflare requires manual MIME parsing and reply construction. The comparison highlights MailKite's ease of integration for autonomous email agents. 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": "