01The limits, as the code enforces them
These are not marketing numbers. They are the *_LIMITS constants the send path enforces, imported into this page, so what you read here is what your instance will refuse.
Fifty recipients everywhere; the message ceiling is the number that differs, and it is measured on the rendered message rather than on your attachment.
Ceilings read from CLOUDFLARE_LIMITS, SES_LIMITS, RESEND_LIMITS and SMTP_LIMITS in @mailysend/providers.
| Transport | Message | Attachment | Recipients |
|---|---|---|---|
| Cloudflare Email Service | 5 MiB | 5 MiB | 50 |
| Amazon SES | 40 MiB | 40 MiB | 50 |
| Resend | 40 MiB | 40 MiB | 50 |
| Raw SMTP relay | 25 MiB | 25 MiB | 50 |
- Events
- Yes — a Queues subscription per sending domain.
- Best for
- A Cloudflare deployment that wants no third-party account at all.
- Watch out
- Public beta, so pricing and limits can change. The smallest message ceiling of the four.
- Events
- Only with an SNS configuration set. Without one there are no events.
- Best for
- Large volume, large attachments, and the lowest per-message price.
- Watch out
- Sandbox limits until you request production access, and events are opt-in.
- Events
- Yes.
- Best for
- Keeping an existing Resend account while moving everything else in-house.
- Watch out
- You are still paying a per-message vendor, which may be the point or may not.
- Events
- No. A 250 is all you get; bounces arrive later as DSNs.
- Best for
- An existing relay you already operate, or a provider with no HTTP API.
- Watch out
- No delivery events means no open, click or bounce data from the transport itself.
The ceiling is measured on the rendered message. Not on your attachment, and not on your HTML. Three of the four adapters build the complete RFC 5322 message, sign it, and measure the bytes before anything touches the network. Attachments are base64 in a MIME part, which costs about a third on top of their raw size, and text parts are quoted-printable, which costs a little more again on non-ASCII content. A 4 MB PDF is comfortably over Cloudflare Email Service’s 5 MiB ceiling by the time it is a message. If you are anywhere near a limit, the number to compare against is roughly four-thirds of your attachment bytes, plus the body.
Resend is the exception, and it is worth knowing. The Resend adapter posts structured JSON rather than raw MIME, so it never renders the message locally and never measures it. Its 40 MB entry is what Resend accepts, not something checked before the request goes out; an oversized message comes back as an HTTP error and is classified from the status code — anything that is not 429, 401, 403 or a 5xx becomes permanent. Same outcome, one network round trip later, and the error text is Resend’s rather than ours.
Recipients are 50 everywhere, deliberately. That is 50 across to, cc and bcc combined, counted before the send. Most relays would accept far more, but a large RCPT list is a spam signal at many receivers and the API contract promises the same number on every transport — consistency is worth more here than squeezing a relay. The header budget is the one place the four diverge quietly, and it only matters if you are stuffing metadata into custom headers.
02Which transports report delivery events
The size ceilings are the difference people notice. Event reporting is the difference that costs them a week, three months in, when someone asks why the bounce chart is empty.
Two of the four report events with nothing to configure, one reports only if you configured it, and one cannot report at all.
| Transport | What comes back | What you have to do |
|---|---|---|
| Cloudflare Email Service | Delivery events by default, over a Queues subscription scoped per sending domain. | Nothing. |
| Resend | Delivery events by default, over its webhooks. | Nothing. |
| Amazon SES | Delivery, bounce and complaint notifications — but only with an SNS configuration set. Without one, SES accepts your mail and tells you nothing. | Set configuration_set in the provider form, with a subscription pointed back at this instance. |
| Raw SMTP relay | Nothing. A 250 says the relay accepted responsibility for the message, and the protocol offers no further callback. | Wire up DSNs to your return path, and publish its MX record. |
A missing SNS configuration set is the single most common reason a migration to SES appears to “lose” deliverability data.
What a DSN gets you, and what it does not. A DSN is a multipart/report; report-type=delivery-status message delivered to your return path, and its machine-readable part carries the original recipient, an action, a status code and a diagnostic string. That is enough to classify a bounce properly and suppress the address. It is not enough to tell you a message was delivered, because a successful delivery generates no report at all — which is why sent is the last thing an SMTP transport can say for certain, and why the return-path MX record stops being optional on this transport. No MX, no DSN, no bounce data of any kind, and only some DSNs arrive at all.
Opens and clicks are the exception. MailySend tracks those itself either way, since they are your own pixel and your own redirect, so those two charts look the same on all four transports and the delivery-side charts do not. But what an open actually means is its own conversation.
03Picking a transport
Four questions, in the order that eliminates the most options soonest.
Every answer is written out below rather than hidden behind the one you pick.
- 01Are you on Cloudflare at all?
If not, Cloudflare Email Service is unavailable and the field is SES, Resend, SendGrid, Postmark or your own relay. If yes, it is the default for a reason: no third-party account, no extra credential, and the events come back on a Queues subscription. The domain has to already be on the same Cloudflare account, and onboarding happens in Cloudflare’s dashboard rather than here — it writes every DNS record itself, so there is nothing to copy.
- 02Do you send attachments over 5 MiB?
Then Cloudflare Email Service is out for those messages — it caps at 5 MiB, and 25 MiB only to verified destinations. SES and Resend accept 40 MB; a raw relay is capped at 25 MiB here whatever your relay would take. Route the heavy sending domain elsewhere rather than capping your whole product, and remember the ceiling is measured after base64.
- 03Do you need bounce and complaint data?
Everyone does, eventually. That rules out raw SMTP as a primary transport and means configuring SES properly rather than minimally: an SNS configuration set at setup time, not after the first campaign. On SES it also means asking AWS for production access — a new account is in the sandbox, which delivers only to addresses you have verified, at 200 messages a day, and looks exactly like a broken instance until you know that.
- 04What does it cost at your volume?
Below a few thousand a month this question does not decide anything: the fixed floor dominates and a vendor free tier is genuinely cheaper. Above a hundred thousand it decides everything, and the answer is usually SES by a wide margin. The panel below runs the pricing page’s own functions rather than restating them.
Runs the same selfHostedCost, sesProviderCost, resendCost and sendgridCost functions the pricing page uses.
The self-hosted figure is an infrastructure bill, not a plan price — there is no upgrade tier to buy. Below a few thousand messages a month the fixed floor dominates and a vendor's free tier is genuinely cheaper; the crossover is the number worth knowing, and it is in the curve rather than in anyone's marketing.
A million messages a month, four ways
| Path | How the price is shaped |
|---|---|
| Self-hosted on Workers | Workers Paid at $5 a month, the first 3,000 messages included, then $0.35 per further thousand, plus storage, queues and analytics — cents below 20,000 messages, around $1.20 at 100,000, around $9 at a million. |
| A domain pointed at SES | Swaps the metered send rate for $0.10 per thousand with no included allowance, and keeps the same $5 floor. |
| Resend | A plan ladder rather than a rate: free to 3,000, $20 to 50,000, $90 to 100,000, then roughly $0.65 per thousand. |
| SendGrid | Flattens to $0.60 per thousand over a floor just under $20. |
Which is why the fourth question decides nothing at 5,000 and decides everything at 500,000.
What a migration actually breaks
| What moves | What happens |
|---|---|
| The size ceiling | A message that has been sending fine for a year starts failing permanently the day you move from SES to Cloudflare. |
| The DNS record set | A domain bound to a transport gets that transport’s rows and nothing else, and rewriting them resets the domain to not_started and clears its last-verified timestamp — so there is a publish-and-verify step in the middle of the migration whether you planned one or not. |
| The return path | From cf-bounce.yourdomain.com to an SES MAIL FROM subdomain to Resend’s send.yourdomain.com. Bounce collection has to be re-established rather than inherited. |
| The signature | Cloudflare and Resend sign with their own keys under their own selectors, SES signs with the key MailySend generated, and a raw relay signs with nothing at all — so MailySend’s own ms1 record has to be published and correct before an SMTP cutover, not after. |
In roughly the order they surprise people.
The way to do it is one domain at a time. Routing is per sending domain, so a migration does not have to be a cutover: publish the new transport’s records, verify them, move one low-volume domain, watch its delivery and bounce rates for a few sends, then move the rest. Moving everything at once means every failure mode above arrives on the same afternoon, and you will not know which one you are looking at.
04What failover actually protects against
Failover is genuinely useful and is routinely expected to do something it cannot.
It covers a transport being unreachable. It does not cover a transport refusing you.
- Moving to a second transport is exactly right
- The messages go out
- Moving to a second transport introduces a reputation problem to a sending identity that had nothing wrong with it
- You now have two damaged reputations and the same underlying list
The router enforces that rule rather than trusting it. Every send error is classified into one of six kinds, and only two of them are allowed to reach a second transport.
| Classification | What the router does | Why |
|---|---|---|
| permanent | Stop. No retry, no failover. | A message that will never be accepted as written is not accepted anywhere else either. |
| transient | Retry here, then a second transport. | One of the two kinds that may fail over. |
| throttled | Wait the indicated delay, then failover is fine. | The other one. |
| auth | Stop and alert. | Credentials do not improve with a retry. |
| suppressed | Stop. | Mirrored inward as a suppression of our own. |
| unknown | Retry here at most. Never a second transport. | The message may already be on the wire. |
Two attempts, not five. A routed send tries at most two transports by default. A third rarely helps, and every additional attempt widens the window in which a duplicate could occur. Pinning a transport on the send call disables failover entirely, which is the point of pinning — silently using another would violate an explicit instruction. Underneath, a per-transport circuit breaker opens after five consecutive failures and half-opens after thirty seconds, letting one request through to find out whether the transport recovered. Its job is not to protect the provider; it is to stop the whole queue’s retry budget being spent on a transport that is currently down while the healthy one sits idle.
The other thing worth knowing: routing is per sending domain. Keeping marketing volume on a different domain from transactional mail — and therefore, if you want, on a different transport — is the standard way to stop a campaign’s complaint rate from affecting whether password resets arrive.
What just happened
You have picked a transport for reasons you can state: a message ceiling that fits what you actually send, an event story you can live with, and a price you have seen at your volume. The two facts most likely to bite later are that the size ceiling is per transport — a message that sends today can fail after a switch — and that SMTP reports no events at all.