Wraps Logo
Amazon SESCloudflare Email Routing

Amazon SES vs Cloudflare Email Routing

Both let an application receive mail and run code on it. One is a receipt-rule engine wired into the rest of AWS; the other is a free forwarder that hands a raw message to a Worker and does not send at all.

This comparison looks lopsided until you notice what the person asking actually wants, which is usually not an email product at all. They want mail addressed to their domain to turn into a function call. Both of these do that and they do it from opposite directions: one is a receipt-rule engine inside a cloud you are probably already using, the other is a free forwarding layer bolted to the DNS you already run. The decisive difference is that only one of them will also send the reply.

At a glance

 Amazon SESCloudflare Email Routing
What it isRaw sending infrastructure from AWSFree inbound email routing into a Worker you write
Published pricing$0.10 per 1,000 emails on à la carte. New accounts are defaulted to the Essentials plan at $0.16 per 1,000; Pro is $105/mo + $0.22 per 1,000 and Enterprise $500/mo + $0.23 per 1,000. Plans are per-account, per-Region.Email Routing is free: 200 routing rules per domain, 200 destination addresses per account, 25 MiB per message. Outbound is a separate Cloudflare Email Service with its own quota, and Workers CPU limits apply on the free plan.
Best forTeams already on AWS with an engineer willing to own the plumbing.Receiving agent mail straight into code you already run on Workers, at no cost.
Watch out forProduction access is an application AWS can refuse, and new accounts start sandboxed to verified addresses. No dashboard, no templates, and nothing stores your events until you build it.Routing is inbound only, so most setups still need a sending provider alongside it. There is no mailbox and no threading — only the handler you write and whatever you decide to store.

Prices are each vendor's published list prices, verified August 2026. Check the vendor's own page before you sign anything.

Where they actually differ

What each product is willing to do

Amazon SES

SES handles both directions. Inbound arrives through receipt rules you attach to a verified domain, and each rule can drop the raw message into object storage, invoke a function, publish to a topic or bounce it outright, with rules evaluated in order. The same account and the same verified identity then sends the reply, which means one vendor, one DNS setup and one bill for the whole conversation.

Cloudflare Email Routing

Cloudflare Email Routing receives and forwards. It matches an address, then either forwards to a destination mailbox you have verified or hands the message to a Worker you wrote. It will not send on your behalf — outbound is a separate Cloudflare product with its own quota — so a system that needs to reply is wiring two things together no matter how you feel about that.

Where your handler runs, and what it may do

Amazon SES

In a Lambda function, with whatever runtime, memory and timeout you configure, inside a VPC if it needs one, holding an IAM role that can reach your database directly. Cold starts are real but the execution model has no unusual limits, and a message that needs thirty seconds of processing gets thirty seconds.

Cloudflare Email Routing

In a Worker, which is a much tighter execution environment: short CPU budgets, no long-lived connections in the traditional sense, and an outbound story that goes through Cloudflare's own bindings. For a fast classify-and-store handler that is a feature, because there is nothing to provision and nothing to keep warm. For anything that wants to sit and think, it is a constraint you will meet quickly.

Large messages and attachments

Amazon SES

SES puts a size ceiling on what it will accept and the standard pattern for anything substantial is to write the raw message into object storage and pass your function a pointer. That indirection is mildly annoying and it is also the right design: your handler never has to hold a large attachment in memory, and the raw source is durably stored for reprocessing when your parser turns out to be wrong.

Cloudflare Email Routing

Cloudflare accepts messages up to a stated size per message and streams the raw content to your Worker. There is no bundled storage behind it, so if you want the original preserved you write it somewhere yourself in the same invocation. That is a few lines, and it is a few lines nobody writes until the first time a parse failure loses a message with no way to replay it.

What it costs to receive a message

Amazon SES

Inbound is metered like the rest of SES, and then the function invocation, the storage and the notifications are each metered separately by their own services. Individually trivial, collectively a bill with several lines on it, and legible only if you already read AWS invoices for a living.

Cloudflare Email Routing

Nothing. Email Routing is free within its published limits on rules and destination addresses, and the Worker invocations fall under whatever Workers plan you are already on. For a side project or an internal tool this is not a marginal advantage, it is the entire argument, and it removes the cost conversation completely.

What each demands of your DNS

Amazon SES

You point MX records at the SES inbound endpoint for the region you chose, and inbound receiving is only offered in a subset of regions, which occasionally forces a split between where you receive and where the rest of your application lives. Your DNS can stay wherever it already is.

Cloudflare Email Routing

Your domain has to be on Cloudflare DNS. That is a small thing if it already is and a real migration if it is not, and it means an email decision is now coupled to your DNS provider. Once there the MX setup is automatic and correct, which removes the most common configuration mistake in inbound mail entirely.

Authentication verdicts and spam

Amazon SES

SES evaluates SPF, DKIM, DMARC, a spam verdict and a virus verdict before your code runs, and passes all of them to the handler. That is unusually useful: your function can refuse a message that failed DMARC for your own domain without you implementing any of the checking, which is how you avoid building a system that trusts forged senders.

Cloudflare Email Routing

Cloudflare performs its own checks and exposes message headers to the Worker, and the ergonomics are closer to being handed the message and told to make your own decisions. It is workable and it puts more of the judgement in your code, which is more control and also more places to get authentication handling subtly wrong.

Failure modes when your code throws

Amazon SES

A failed Lambda invocation behaves like any other failed invocation: retries, dead-letter queues, alarms, the machinery you already have. Because the raw message is usually already in object storage by then, a bug in your parser costs you a reprocessing job rather than a lost message.

Cloudflare Email Routing

A Worker that throws while handling a message causes the delivery to fail, which the sender eventually sees as a bounce. That is arguably the honest behaviour — the mail genuinely was not accepted — and it also means an unhandled exception in a code path you ship on a Friday is visible to people outside your company.

What this looks like a year in

Amazon SES

Usually it has grown into the rest of the account: events on a queue, messages archived with a lifecycle policy, replies sent from the same verified domain, permissions expressed as roles. The cost is the usual AWS cost, which is that a simple thing is now composed of five services and a diagram.

Cloudflare Email Routing

Usually it is still small, which is the point. The pattern that ages badly is the one where the inbound half is free and elegant on Cloudflare while the outbound half lives with an entirely separate vendor, so your domain's authentication records, your suppression logic and your message history are split across two systems that do not know about each other.

Pros and cons, side by side

Amazon SES

Strengths

  • Both directions live in one account, so the message that arrives and the reply that leaves share a verified identity, a DNS setup and a bill.
  • Authentication and spam verdicts are computed before your code runs and handed to it, which is how a handler refuses a forged sender without anybody implementing the checking.
  • The raw message is written to durable storage as a matter of course, so a bug in your parser costs a reprocessing job rather than a message nobody can recover.
  • Your handler is an ordinary function with the runtime, memory, timeout, network placement and database access you configure, which removes a whole class of execution constraint.

Trade-offs

  • Receiving is offered in a subset of regions, so the place you receive and the place the rest of your application lives are sometimes not allowed to be the same place.
  • The cost is spread across the inbound charge, the function invocation, the storage and the notifications, which is individually trivial and collectively an invoice several lines long.
  • Anything substantial arrives as a pointer to an object rather than as a message, so the handler starts with a fetch before it starts with logic.
  • Rules, actions, roles and destinations are all separate pieces of configuration, and the simple thing you wanted is now a diagram with five services on it.

Cloudflare Email Routing

Strengths

  • Receiving costs nothing within the published limits, which for a side project or an internal tool removes the cost conversation from the design entirely.
  • The MX configuration is handled for you and is correct, which eliminates the single most common way an inbound setup silently fails.
  • The raw message streams straight into your handler with nothing to provision and nothing to keep warm, which suits a fast classify-and-store perfectly.
  • Rules and destinations are a short list in one place rather than an ordered rule set with actions attached, which is genuinely easier to reason about at small scale.

Trade-offs

  • It will not send, so anything that replies is wiring in a second product with its own quota, its own authentication records and its own idea of what happened.
  • Your domain has to be on Cloudflare DNS, which turns an email decision into a coupling with your DNS provider and a real migration if you are not there already.
  • The execution environment is tight — short CPU budgets, no long-lived connections in the traditional sense — which is a feature for a quick handler and a wall for anything that thinks.
  • Nothing stores the original unless you write it somewhere in the same invocation, and a handler that throws turns into a bounce visible to the person who mailed you.

Pick in thirty seconds

Choose Amazon SES if

  • The same system has to reply, and you would rather not operate two vendors and two sets of authentication records for one conversation.
  • You want authentication and spam verdicts computed before your code runs, rather than deciding how much to trust a header yourself.
  • Messages can be large, and having the raw source durably stored for replay is worth the extra indirection.
  • Your handler needs a VPC, a long timeout or a direct database connection — things a constrained edge runtime will not give you.

Choose Cloudflare Email Routing if

  • You only need to receive, and something else already sends, so the missing outbound half is not missing anything you wanted.
  • Your DNS is already on Cloudflare and the cost of receiving mail dropping to zero is the deciding factor.
  • The handler is a fast classify-and-store or forward-to-a-human, well inside a short CPU budget.
  • You want the MX configuration to be somebody else's responsibility, because misconfigured MX records are the most common way inbound mail silently fails.

What actually moves when you switch

In roughly the order you would do it. The expensive parts of an email migration are never the send call.

  1. Check whether this is an email migration or a nameserver migration. One of these two requires your domain to be on its own DNS before it will route anything, so if you are not already there the first task is moving authoritative DNS for the whole domain — every record, not just the mail ones — with whatever coordination that implies. The other only wants an MX record pointing at a regional endpoint and does not care who serves your zone.
  2. Translate the rule model rather than copying it. An ordered receipt rule set with actions attached — store the raw message, invoke a function, publish, reject, stop processing — is a different shape from matching an address to a destination or a handler. Catch-all behaviour and the notion of a rule that stops later rules from running have no clean equivalent in the other direction, so write down what each rule is for before trying to reproduce it.
  3. Rewrite the handler's entry point, because the two are not the same function with a different signature. One side receives an event carrying verdicts for SPF, DKIM, DMARC, spam and viruses already computed. The other receives the message and expects you to form your own opinion from the authentication headers. Any code branching on a verdict has no counterpart and has to be reimplemented, and getting that wrong means trusting a forged sender.
  4. Decide where the original is stored before you move, not after. Durable storage of the raw message is automatic on one side and is a few lines you write in the handler on the other. Those few lines are the difference between a parse failure you replay and a parse failure that lost a customer's email, and nobody writes them until the first time it happens.
  5. Compare the accepted message sizes against your real traffic. The ceilings differ, and the failure is not a truncated message but a rejected one, which the sender sees. Sample what you actually receive — invoices with attachments and forwarded threads are the usual offenders — rather than reasoning from what you expect people to send.
  6. Verify every forwarding destination if you are moving toward the free forwarder, and budget for the humans involved. Destination addresses have to be confirmed by whoever owns them clicking a link, which is fine for your own mailbox and slow for a shared inbox at a client. It is the one step in this migration whose duration depends on somebody else reading their email.
  7. Plan the outbound half explicitly, including the DNS consequences. If the sending moves to a different vendor from the receiving, your SPF record has to name that vendor, your DMARC alignment has to hold for it, and the message identifiers your inbound side preserves have to be the ones your outbound side references or threading breaks. Two systems now know half the story each, and nothing joins them unless you build it.
  8. Rewrite the failure handling around the new semantics, because they are opposites. On one side a failing handler is a failed invocation with retries, a dead-letter queue and an alarm, and the message is already stored. On the other a handler that throws causes the delivery itself to fail, which the sender eventually sees as a bounce. Code written against the forgiving model ships an outage that is visible outside your company.

Questions to ask before you sign

Put these to a sales rep, or find the answer in the documentation before anyone quotes you a number.

  1. Does the feature we are building ever reply, and if so which product is sending that reply and under whose authentication records?
  2. Is our domain already on the DNS provider this requires, and if not who owns the zone migration?
  3. What is the largest message we actually receive today, and have we checked it against the accepted size on both sides?
  4. Does our handler need a database connection, a long timeout or a private network, and does the runtime we are choosing allow any of that?
  5. When our parser fails, do we want a bounce the sender sees or a retry we handle — and which one are we about to get?
  6. Who verifies each forwarding destination, and how long will waiting for those people take?
  7. Are we happy computing our own authentication judgement from headers, or do we want the verdicts handed to us?

Common questions

Can Cloudflare Email Routing send email?

Routing itself cannot. It receives, matches and forwards, and a Worker can reply only through a separate Cloudflare sending product with its own quota, or through an outside provider you call from the Worker. Plan for that from the start: if the feature you are building is reply-by-email, the inbound half being free does not mean the whole feature is, and the sending half is where domain authentication and reputation actually live.

Is SES inbound available in every region?

No, and this catches people out. Inbound receiving is offered in a subset of the regions that support sending, so if your application runs somewhere that does not offer it you either accept a cross-region hop or run receiving in one region and everything else in another. Check the region list before designing around it, because discovering the gap after the architecture diagram is drawn is an unpleasant afternoon.

How do I test an inbound pipeline without a real sender?

Send yourself real mail from several different providers, because the differences that break parsers are exactly the ones a synthetic fixture will not have: multipart structures, inline images, quoted-printable encoding, mobile clients that top-post. Save the raw sources you receive as test fixtures afterwards. A parser tested only against messages your own code generated will fail the first time somebody replies from Outlook.

Which one is better for a support inbox feature?

It depends on whether replies come from the same feature. A shared inbox where agents answer from the product needs sending, threading and a suppression story, which pulls you toward the option that also sends. A pipeline that only ingests — turning mail into tickets that get answered somewhere else entirely — is a good fit for the free forwarder, and the ingest half will stay simple.

Can I use both, inbound on one and outbound on another?

You can, and plenty of teams do, but be deliberate about the seams. Your SPF and DMARC records describe who may send as your domain and have to cover the sending vendor specifically. Threading depends on message identifiers that your inbound side has to preserve and your outbound side has to reference. And you now have one system that knows about bounces and another that knows about replies, with nothing joining them unless you build it.

Do I have to move my DNS to use the free option?

Yes, and it is worth naming as the real cost rather than a detail. Routing requires the domain to be on Cloudflare's authoritative DNS, which means moving the whole zone — web records, verification tokens, existing mail records, everything — not just the parts concerned with email. If your domain already lives there this is nothing. If it does not, an inbound email decision has just become a change with a blast radius across every service that resolves your name.

What happens if my handler crashes?

Opposite things, and this is the difference most likely to surprise somebody porting code. A failing function invocation on the AWS side retries, lands in a dead-letter queue and raises an alarm, with the raw message already stored so you can reprocess it once the bug is fixed. A Worker that throws while handling a message causes the delivery itself to fail, and the sender eventually receives a bounce. The second behaviour is arguably more honest and it is also a Friday deploy that strangers can see.

Which one gives me SPF and DMARC results?

The AWS side computes SPF, DKIM, DMARC, spam and virus verdicts before your code runs and passes them to the handler, so refusing a message that failed DMARC for your own domain is a conditional rather than a project. The Worker route exposes headers and expects you to interpret them, which is more control and more surface to get subtly wrong. If your inbound feature makes any trust decision at all — acting on a reply, accepting a command, matching a sender to an account — this dimension matters more than cost.

Can I migrate an inbound pipeline without losing mail?

Not cleanly with a single MX switch, because during propagation some senders will resolve the old records and some the new. The approach that works is to stand up the new path on a subdomain first, prove the handler against real mail from several providers, then change MX and keep the old path alive and monitored for at least a day afterwards. Keep both sets of raw messages for that period so anything that lands on the wrong side can still be reprocessed rather than reconstructed from a log line.