What the Bounce Says: SPF and DMARC Bounce Codes From 21 Mailbox Providers
In part one, we asked 21 mailbox providers what they do with a broken SPF record. This part is about what they say. An SMTP refusal gives the sender a reply code and a line of text, and for most senders that reply, plus whatever their sending software makes of it, is the only explanation they'll ever get.
We sent the same 13 SPF configurations along two paths: from our own server, which logged every SMTP reply as it happened, and through Mailgun, which records the receiver's reply and adds its own reading of it. Of about 3,800 test messages, one per case and recipient, nearly 2,400 never got through. Here is what the receivers said about them, and how much of it a sender can use.
Disclosure: DMARKOFF builds DMARC monitoring tools. The seed mailboxes came from GlockApps, which is made by the same team. The Mailgun account was an ordinary customer account. We owned all domains, IPs, and accounts, and the data is on GitHub in the same folder as part one.
TL;DR
- One failure, three kinds of explanation. Refusals for our broken-SPF cases cited DMARC at Gmail (
5.7.26), Microsoft (5.7.509), Yahoo (5.7.9) and most other large providers; SPF at Interia, Naver and Onet; and nothing useful at Barracuda and mailo. Only Gmail and Onet used the enhanced status codes the standards define for these failures. - Most refusals on our own server were about the IP. 1,692 of 2,147 refused messages hit a blocklist or a dynamic-IP policy, all before the receiver saw the message, and several carried the same
5.7.xcodes as authentication failures. The SMTP step and the text told them apart; the enhanced code alone didn't. - Our server never received a bounce message. Every refusal arrived during the SMTP session, and accepted-then-dropped messages produced nothing at all. Through Mailgun, Microsoft 365 accepted messages and bounced them 2 to 17 seconds later, so the same message shows up as delivered and then as failed.
- Mailgun filed the same failure under different reasons. Replies that mention DMARC became
espblock; those that mention SPF becamegeneric. Its bounce classification report counted 89 of the 170espblockrefusals, and none of the 188 permanent failures put a recipient on its suppression list. - A temporary error can hide a permanent mistake. ukr.net and Interia answered some broken records with
451until the sender gave up: our server after five or six attempts, Mailgun after eight hours, with its own602 Too old.
Two places to read a bounce
A sender can learn about a failed delivery in three ways: the SMTP reply that refuses the message, a bounce message the receiver sends after accepting it, and, when the mail goes through an ESP, the failure event the ESP records. This test produced all three.
On the direct path, our server was the SMTP client. It logged every reply together with the step it came at: the greeting when we connected, the sender address (MAIL FROM), the recipient (RCPT TO), or the end of the message body (DATA), after the whole message had been transmitted. Each case domain's MX pointed back at the same host, where a small bounce server waited for delivery status notifications (DSNs), the formal name for those later bounce messages.
On the Mailgun path, Mailgun acted as the SMTP client. Its event log also keeps the receiver's reply, plus Mailgun's interpretation: an event (delivered or failed), a severity (permanent or temporary), a reason, the attempt number, and Mailgun's name for the recipient's provider. We retrieved the full event log through Mailgun's API on 25 October, two days after the run, and queried its bounce classification report the same way.
Both paths carried the same failures: SPF that didn't pass, DMARC at p=reject, and no aligned DKIM signature. What differed was what each path showed us. We count messages, one per case and recipient, by how they ended; where a number counts attempts or events instead, we say so.
What receivers said when SPF broke DMARC
These are the permanent refusals for the failing cases. Unless noted, they come from the Mailgun run, where no receiver refused us for the IP:

The include= typo through Mailgun: twelve receivers refuse the same message, citing DMARC, SPF, or neither (Mailgun log, one row per receiver, recipients hidden, times UTC+3)
Most of the big providers say DMARC, and that's accurate: the message failed DMARC because SPF failed and there was no aligned DKIM signature to pass it instead. It also means the reply never names what actually broke. A sender who gets 5.7.26 from Gmail learns that authentication failed, not that an include points to a name without an SPF record or that a typo turned include: into includes:.
Interia, Naver, and Onet point at SPF. Onet varied its reply by test case: a DMARC message for the lookup limits, the missing include, and the mistyped mechanism, an SPF message for the fail case, and a bare Rejected for two records and no record. Mailo and Barracuda say nothing about the cause at all.
The codes don't help much
RFC 7372 added enhanced status codes for exactly these failures: X.7.23 for an SPF fail, X.7.24 for an SPF error, and X.7.26 when more than one authentication check failed. Two receivers used them: Gmail sent 5.7.26 with its DMARC refusals, and Onet 5.7.23 for the fail case.
Nobody used X.7.24 for a permerror. Yahoo's 5.7.9 belongs to SMTP AUTH, the login step between a mail client and its server, and Naver's 5.7.2 to mailing lists. Sorted by enhanced code, our SPF failures fall into nine buckets, counting the replies that have no enhanced code at all.
What it means for you: use the basic code to tell a temporary refusal (4xx) from a permanent one (5xx). For the cause, read the reply text together with the enhanced code and the SMTP step; the code alone won't tell you whether SPF, DKIM, or your IP was the problem. And keep the codes your ESP adds, like Mailgun's 602, apart from what the receiver said.
Most refusals were about the IP
On the direct path, our address sat in Spamhaus PBL (see part one), and 1,692 of the 2,147 refusals had nothing to do with SPF:
They carry the same 5.7.1 and 5.7.2 as the authentication refusals above, and Yahoo calls a permanent refusal "permanently deferred". What sets them apart is when they arrive and what they hit. 406 came when we connected, 999 at MAIL FROM, and 287 at RCPT TO, all before the receiver had seen a single header, and they hit the clean controls exactly like the broken records. Of the 455 authentication refusals, 425 came after the message body. The remaining 30 came at RCPT TO, all from Interia and Onet.
What it means for you: if the reply cites an IP blocklist or a dynamic-IP policy, deal with that restriction first; no SPF change will lift it. In our test, these refusals also hit the clean controls, which is what confirmed them, but a refused control on its own isn't proof of a reputation problem.
Accepted, then bounced, or never heard of again
Our bounce server ran from before the pilot until we read its log on 25 October, and received nothing from any receiver: no DSN for any of the 1,033 messages accepted on the direct path. That includes the ones that never reached a mailbox. Seznam accepted 20 failing messages with 250 OK and dropped them, and Microsoft did the same with the five messages it let through from our blocklisted address. None of them came back.
Through Mailgun, one receiver did send bounces. Microsoft 365 accepted 17 messages with 250 2.6.0 … Queued mail for delivery and returned each of them 2 to 17 seconds later with 550 5.7.509. Mailgun matches those bounces to the original message, so its log shows the same message twice: first delivered, then failed. A day and a half later, Microsoft 365's aggregate reports listed 14 of those 17 messages with the disposition reject, and Outlook.com's listed all 34 messages it had refused during the session.

One message to Microsoft 365 in Mailgun's log: accepted, delivered three seconds later, then failed when Microsoft's bounce arrived 17 seconds after that (times UTC+3, recipient hidden)
Naver, Seznam, and Amazon WorkMail never sent anything back for the failing messages they accepted and dropped. Naver only did that through Mailgun; on the direct path, it refused the same cases outright.
What it means for you: in an ESP's log, delivered isn't necessarily final, and the absence of a bounce doesn't prove delivery. For the drops, the SMTP log showed only that the messages were accepted; the seed mailboxes showed that they never arrived, and Seznam's aggregate reports recorded them as reject. Naver sent no reports, so its drops showed up only in the seed test.
DMARKOFF collects these aggregate reports and shows each receiver's verdict by reporter, including the rejects that never came back as a bounce.
Temporary errors that didn't clear
Two receivers answered broken records with temporary errors:
- ukr.net returned
451 4.1.0 Temporary error during SPF verificationfor the include that points to a name without an SPF record, which RFC 7208 §5.2 calls a permanent error. - Interia returned
451 4.4.3 … Deferred due to SPF with invalid contentfor the mistyped mechanism and451 4.4.0 … Deferred due to SPF unsafefor the domain without a record, while it refused the lookup-limit and missing-include cases outright with5.5.2.
A temporary error tells the sender to try again, so both paths did. Our server retried in later passes, 10 to 80 minutes apart, and stopped after five or six attempts, a little over two hours after the first. Mailgun retried 10, 20, 35, 65, 125, and 245 minutes after the first attempt and gave up at the eight-hour mark with 602 Too old. That code is Mailgun's own: the receivers only ever said 451.
The same logs also held temporary errors that were really temporary. Interia greylisted new senders (451 4.7.1 … Greylisting in action, 51 times on the direct path), freenet did the same through Mailgun (457 Greylisted, please come back later), and Gmail rate-limited our new domains (421 4.7.28 Gmail has detected an unusual rate of mail originating from your SPF domain, 46 times). All of those went through on a later attempt. Virgilio's 451 too many messages, slow down and Vodafone's 421 4.3.2 All server ports are busy were about load.
What it means for you: a deferral that names SPF and comes back unchanged on every retry deserves a look at the record before the retry window runs out. In our test, the records stayed broken, so no retry could succeed.
How Mailgun reads the same refusals
Mailgun files every failure under a reason. In our run, the failures fell under three reasons, one of them with both severities:
The espblock refusals came from Gmail, Google Workspace, Outlook.com, Microsoft 365, the Yahoo and GMX families, Apple, Mail.ru, Yandex, Zoho, La Poste and Onet (its 5.7.0); the permanent generic ones from Interia, Naver, Onet (its 5.7.23 and Rejected) and Barracuda.
The label follows the wording of the reply more than its cause. Onet's replies to our broken records landed under two reasons, and a team that watches only espblock, the reason that reads as "the receiver blocked us on policy", would miss every SPF-worded refusal.
Mailgun's bounce classification report, which groups failures by provider and category, was narrower still. Asked for our 13 domains, its API returned 89 critical bounces, all ESPBLOCK, under six provider labels: Gmail, Google Workspace, Yahoo, AT&T, Outlook US and Outlook EU. The other 81 espblock refusals, from Microsoft 365, Apple, GMX, Mail.ru, Yandex, Zoho, La Poste, mailo, and Onet, and all the generic ones, weren't in it.
None of the test refusals added an address to Mailgun's suppression lists. We emptied the lists of all 13 domains right before the run, and two days later they were still empty: none of the 188 permanent failures stopped Mailgun from sending to that address again.
An invalid address triggered suppression the day before, during warm-up. A freenet address bounced with 550 unrouteable address from two of our domains, and Mailgun then refused to send to it from two others as well, with 605 Not delivering to previously bounced address. We cleared the lists after that, before the run.
What it means for you: if you send through an ESP, watch all of its failure reasons, not only the one named for blocks, and don't take its classification report for the full list. Mailgun kept sending to recipients who had refused us on DMARC, which is what you want once the record is fixed; check how your own ESP handles it before you rely on that.
What to do with a bounce
Most teams watch the bounce rate, not the bounce text. In this test the text was usually what told an SPF problem from a blocklisted IP, and the receivers that dropped mail silently sent nothing to read at all.
Aliaksandr MarkauCo-founder & CTO at DMARKOFF and GlockApps
- Use each part of the reply for its own job. The basic code says whether to retry; the text, the enhanced code, and the SMTP step together say why. Keep the codes your ESP adds, like Mailgun's
602, apart from the receiver's. - Check whether it cites an IP blocklist or a dynamic-IP policy. If it does, deal with that restriction; no SPF change will lift it.
- When the reply cites DMARC, check alignment. It means neither SPF nor DKIM passed with alignment to the From domain. Gmail (
5.7.26), Microsoft (5.7.509), and Yahoo (5.7.9) say it with different codes, so go by the text. - Investigate SPF deferrals that repeat. If the same
451naming SPF comes back on every retry, check the record, and fix it while the sender is still retrying. - Watch for failures after
delivered. Our Microsoft 365 messages turned from delivered to failed 2 to 17 seconds later; that's what we saw, not a safe waiting period. - Use DMARC aggregate reports and seed tests for the drops no bounce reveals. Seznam reported every silent drop as
reject; Naver sent no reports, and only the seed test showed its drops. - Know your ESP's categories and suppressions. Look at every failure reason, and after fixing a problem, check that no recipients were suppressed because of it.
You can check a record before you publish it with DMARKOFF's SPF checker.
Limitations
- One blocked IP. Our direct-path address was in Spamhaus PBL, so most large receivers refused it before evaluating authentication; their authentication replies come from the Mailgun run only.
- Built around SPF and DMARC. The test targeted SPF failures under
p=rejectwithout aligned DKIM. The IP blocks, greylisting, rate limits, and the dead warm-up address were side findings, not scenarios we designed, and spam filtering, unknown users or full mailboxes would produce other replies. - One ESP, one run. Mailgun's reasons and reports are what one account showed for one run in October 2026, and its classification may change.
- Small samples. On the Mailgun path, we had one or two seeds per mailbox type.
Methodology and data
Dates. The same runs as part one: a pilot on 21 October 2026, direct runs on 22 and 23 October, the Mailgun run on 23 October. We read Mailgun's event log and bounce classification report through its API on 25 October, and checked seed mailboxes through the same day.
Direct path. Our sender recorded every attempt with the SMTP step, code, and reply, and retried temporary refusals up to five times, 10 to 80 minutes apart. A bounce server on the sending host, the MX of every case domain, logged incoming mail from before the pilot until 25 October.
Mailgun path. We matched Mailgun's events to test cases by Message-ID and recorded the reason, severity, codes, and the receiver's reply for each.
Seeds. We started the seed tests and collected their results through the GlockApps MCP server, from an AI assistant that matched them to the SMTP and Mailgun logs case by case.
Reports. We read the receivers' aggregate reports through the DMARKOFF MCP server grouped by reporter and by report window, from the same assistant.
Counting. A message is one case sent to one recipient, counted once by how it ended: accepted, refused, or still deferred when the retries stopped. Attempts and Mailgun events are counted separately where the text says so.
Classification. A permanent refusal counted as about the IP when its text named our address, a blocklist, or a dynamic-IP policy. The rest counted as SPF or DMARC refusals: most say so, and the few that give no reason (mailo, Barracuda, Onet's Rejected) came only for the failing cases, from receivers that accepted the controls.
Data. smtp.csv (direct runs) and events.csv (Mailgun run), with recipients reduced to their domain, the bounce server's record, Mailgun's classification report and suppression lists, header excerpts, and bounces.py, which reproduces every count in this article, are in dmarcoff/articles on GitHub.
FAQ
Gmail refused the message because it failed DMARC: neither SPF nor DKIM passed with alignment to the From domain, and the domain publishes p=reject. In our test, Gmail sent it for every broken record it didn't accept. 5.7.26 is the registered code for "multiple authentication checks failed".
It's Microsoft's refusal due to a DMARC failure under p=reject. Outlook.com and Hotmail sent it during the SMTP session; Microsoft 365 accepted the message first, then bounced it a few seconds later. 5.7.509 is Microsoft's own code, not a registered one.
Yahoo, AOL, and AT&T refused the message on DMARC. The registered meaning of 5.7.9 is about SMTP AUTH and doesn't explain this refusal; the reply text identifies DMARC as the cause.
Yahoo refused the sending IP itself, before looking at authentication. Despite the word "deferred", it's a permanent refusal: the reply adds "Retrying will NOT succeed". In our test, it came at MAIL FROM for every message from our address, which was listed in Spamhaus PBL.
It's the reason code Mailgun assigns to refusals it attributes to the receiver blocking the message. In our run, it covered every refusal that mentioned DMARC, plus mailo's Security issue, but not the ones that mentioned SPF, which Mailgun filed as generic.
The receiver accepted it during the SMTP session and sent a bounce afterward. Microsoft 365 did this with every DMARC failure in our Mailgun run, 2 to 17 seconds after accepting the message.
In our run, Mailgun stopped retrying after eight hours of temporary errors and logged 602 Too old. It's Mailgun's code, not the receiver's. The receivers behind it, ukr.net and Interia, were answering broken SPF records with 451.
DMARKOFF is a DMARC monitoring platform built by the team behind GlockApps. We run tests like this one on our own domains and publish the data behind every claim.



