Comparisons

Cloudflare Email Routing, and what you still have to build

Cloudflare Email Routing is free and forwards mail well. The gap opens when your app needs parsed bodies, stored attachments and threaded replies.

Published 7 min read

Cloudflare Email Routing’s documentation and pricing last read 30 July 2026

The short version

  • Cloudflare Email Routing is free, forwards well, and is the right answer if a person reads the mail. Genuinely — start there.
  • The gap is not receiving the message; it is MIME parsing, attachment storage, retries, threading and sending, all of which become code you own inside a Worker.
  • It requires your domain to be on Cloudflare's nameservers. Inboundr needs two records in whatever zone you already have.
  • Running both is common: free forwarding for human addresses, a subdomain on Inboundr for the machine ones.

What Cloudflare Email Routing actually gives you

If your domain is on Cloudflare, Email Routing is a checkbox away and it costs nothing. You create rules mapping addresses on your domain to destination addresses you have verified, Cloudflare publishes the MX records for you, and mail starts flowing. There is also a catch-all, and there are Email Workers — a Worker invoked with the inbound message instead of an HTTP request, which can forward it, reject it, or reply to it.

It is a genuinely good piece of free infrastructure, and for a large number of people reading this page it is the correct answer. We would rather say that in the first paragraph than have you discover we left it out.

The reason there is a comparison to make at all is that “a Worker can receive the message” and “your application can work with the mail” are separated by a surprising amount of engineering, and the gap is invisible until you are in it.

The Worker you end up writing

An Email Worker hands you the message as a stream. Here is where that starts, and it looks encouragingly short:

worker.js — the optimistic version
export default {
  async email(message, env, ctx) {
    // message.from, message.to and message.headers are available.
    // message.raw is a ReadableStream of the full RFC 5322 message.
    await fetch("https://your-app.example.com/hooks/inbound", {
      method: "POST",
      body: message.raw,
    });
  },
};

That works, and it is also the moment the problem moves rather than gets solved: your application now receives raw MIME over HTTP and has to parse it. So the parsing moves into the Worker, and the list of things you own grows:

  • A MIME parser in the Worker. Multipart bodies, quoted-printable and base64 encodings, character sets that are not UTF-8, HTML alternatives, inline images referenced by Content-ID. Real mail from real clients breaks parsers in ways test fixtures do not.
  • Attachment storage. A webhook body is the wrong place for a 20 MB PDF, so attachments go to R2 — which means bucket lifecycle rules, key naming, and signed download URLs with expiry.
  • Retries, with state. If your endpoint is down, the Worker has one chance. Doing better means a queue, a backoff schedule and somewhere to keep the message meanwhile, because the message itself is not retained anywhere.
  • Threading. Grouping a reply with the message it answers means storing Message-ID, In-Reply-To and References and resolving them — a table, an index and the edge cases where a client sends neither header.
  • Sending, separately. Replying to the message currently being handled is supported. Replying an hour later, or sending anything not in reply to a live invocation, needs a different provider entirely — so you are back to SES, Resend or Postmark for the outbound half, with its own DKIM setup.
  • Spam decisions. Whether to act on a message that failed SPF or looks like spam is a judgement your Worker has to make, from the headers, on its own.

The honest framing

None of this is hard, exactly. It is a week or two of work, and then it is a component in your system that handles untrusted input from the open internet and has to keep working while you are busy with the product. The question is not whether you could write it. It is whether it should be yours.

Side by side

Where the Cloudflare column says partly, it almost always means “possible, in a Worker you write and maintain”. That is a real answer, not a dodge — for some readers it is the preferred one.

Price for a low-volume domain

Inboundr
A free plan with a small monthly message allowance and a short retention window; paid plans past that.
Cloudflare Email Routing
Free, with no message allowance to run out. This is a genuine advantage and no amount of feature comparison changes it.

Works with your existing DNS provider

Inboundr
Two records — MX and DKIM — added wherever your zone already lives. Registrar and nameservers stay where they are.
Cloudflare Email Routing
The domain has to be on Cloudflare, using Cloudflare's nameservers. For many people that is already true; if it is not, this is a migration of your whole zone, not two records.

Forward to a mailbox a person reads

Inboundr
A forwarding endpoint re-sends the original message to one address or a group.
Cloudflare Email Routing
Per-address rules and a catch-all, forwarding to destination addresses you verify first. Solid, and the reason most people turn it on.

The parsed message delivered to your app

Inboundr
An HMAC-signed webhook with subject, text and HTML bodies, addresses, threading ids, attachment metadata and spam and SPF/DKIM verdicts, already extracted.
Cloudflare Email Routing
An Email Worker receives the message as a raw stream. Getting fields out of it means running a MIME parser inside the Worker, within its CPU and memory limits, and maintaining that yourself.

Messages stored and fetchable later

Inboundr
Every message is stored for the plan's retention window and can be listed, fetched and re-delivered over the REST API.
Cloudflare Email Routing
Nothing is retained. If your Worker errors, or your service was down when it ran, the message is gone — unless the Worker itself wrote a copy to R2 or D1, which is code you own.

Attachments as fetchable objects

Inboundr
Stored individually and handed out as short-lived download links, so a 20 MB PDF never has to travel through your webhook body.
Cloudflare Email Routing
Extract them from the raw MIME in the Worker and put them somewhere yourself. Buckets, keys, lifecycle and download URLs are all your build.

Send a threaded reply from code

Inboundr
Name the message you are answering and the threading headers are written for you. Replies go out authenticated and signed with your domain's DKIM.
Cloudflare Email Routing
A Worker can reply to the message it is currently handling. There is no general sending API, no way to reply to a message from an hour ago, and no thread to reply into.

Conversations grouped into threads

Inboundr
Messages are threaded on arrival from Message-ID, In-Reply-To and References, so an agent gets the conversation rather than a stack of unrelated mail.
Cloudflare Email Routing
No concept of a thread. Each invocation sees one message with no memory of the last one.

An AI agent that can work its own inbox

Inboundr
A hosted MCP server on the same API key: list, read and reply as tool calls, no glue code.
Cloudflare Email Routing
Not offered. You would be building the store, the API over it and the MCP server on top of Workers, R2 and D1.

Visibility when something breaks

Inboundr
The console shows the parsed message, every delivery attempt, the response your endpoint gave and the reason a retry is queued.
Cloudflare Email Routing
Routing status in the dashboard and Worker logs through Cloudflare's own tooling. Correlating a sender's 'I emailed you' with a specific invocation is the work.

When Cloudflare Email Routing is the better choice

  • You want forwarding, and nothing else, for free. Addresses on your domain landing in your existing mailbox, at no cost, with your DNS already on Cloudflare. There is no argument to make against that and we are not going to invent one.
  • Your domain is already on Cloudflare and you value one vendor. DNS, CDN, Workers, R2 and mail routing on one bill and one dashboard is a real operational simplification.
  • You want to own the pipeline. Some teams genuinely prefer their mail handling to be code in their repo, deployed with everything else, with no third party in the path. Email Workers is a good place to do that.
  • Volume is high and the processing is trivial. If you receive a great deal of mail and all you do is check one header and drop most of it, per-message pricing is the wrong shape and a free Worker at the edge is the right one.

Using both together

These are not mutually exclusive, and the combination is a reasonable destination rather than a compromise. Keep your zone on Cloudflare — Email Routing and an inbound MX record for the same domain are the one thing that does conflict, since a domain has one set of MX records and they point at one receiver.

So the split is per domain or per subdomain:

  • Cloudflare Email Routing on yourdomain.com for the handful of human addresses — hello, billing, you.
  • Inboundr on mail.yourdomain.com or a dedicated domain for the machine addresses — ticket intake, agent inboxes, per-customer reply addresses. DKIM is per domain, so the subdomain signs independently and a reputation problem on one does not touch the other.

Plenty of accounts run exactly this way. Nobody should move their personal mail off free forwarding to make a comparison page tidy.

Moving an address across

Because a domain has one MX record set, moving is a cutover rather than a gradual roll. The sequencing below avoids a window where mail bounces:

  1. Decide the hostname first. If any address on the domain should stay on Cloudflare forwarding, use a subdomain for us — that turns a cutover into an addition, and step four stops being risky.
  2. Add the domain or subdomain in the console and publish the DKIM record in Cloudflare DNS. This does not affect delivery, so it is safe while Email Routing is still live.
  3. Create the endpoint that will receive the mail — a webhook, or a forward to the same mailbox Cloudflare was delivering to if you want a like-for-like swap first.
  4. Lower the TTL on the existing MX record, wait for the old value to expire, then replace it with ours. Disable the Email Routing rules afterwards, not before.
  5. Send a test message and confirm it arrives at the endpoint. The console shows the parsed message and the delivery attempt.
  6. Keep the Cloudflare rules disabled but present for a week. Turning them back on and restoring one MX record is a five-minute rollback.

Questions people ask

Can I keep my DNS on Cloudflare and still use Inboundr?

Yes, and most people do. We need two records in your zone; whose nameservers serve that zone is irrelevant to us. The constraint runs the other way — Email Routing requires the domain to be on Cloudflare, we do not.

Is an Email Worker not basically a webhook?

It is the delivery mechanism, not the payload. A webhook from us arrives as parsed JSON with bodies, verdicts and attachment links, signed, retried, and backed by a stored copy you can fetch again. An Email Worker arrives as a byte stream with one attempt and no storage. The difference is everything between those two sentences.

Can Cloudflare store messages in R2 for me?

A Worker you write can write to R2 — Cloudflare does not do it for you, and R2 holds bytes rather than a queryable mailbox. “List every message from this sender this week with its attachments” is a schema, an index and an API over that bucket. That is the layer being compared here.

I want an AI agent to handle the mail. Does that change the answer?

It sharpens it. An agent needs the message reduced to text it can reason about, the conversation it belongs to, and a way to reply in-thread — which is the whole list of things an Email Worker does not give you. It also needs guardrails, because an inbox is an unauthenticated write into a model’s context; our guide to giving an agent its own address covers those in detail.

What about sending?

Email Routing is receive-only apart from replying to a message a Worker is currently handling. If your application needs to send — replies later, notifications, anything outbound — that is a separate provider and a separate DKIM setup. Inbound and outbound on one domain identity is part of what you are choosing between.

Would I be better off with a raw cloud mail receiver instead?

Possibly — for an application workload it is a fairer comparison than Cloudflare, because you get the raw message and full control over what happens next. It is also the same list of things to build as the Worker above, minus the edge runtime: parsing, storage, retries, threading, and a separate outbound path with its own bounce handling. The choice is not really which cloud receives the mail; it is whether that layer should be yours to maintain.

Claims about Cloudflare Email Routingwere checked against Cloudflare’s public documentation for Email Routing and Email Workers. Read on 30 July 2026. If something here is out of date, tell us and we will correct it.