Security

168 Domains Have SPF Records That Look Fine and Silently Don't Work

We expanded the SPF records of the Tranco top 10,000 domains. 2.2% exceed the RFC 7208 ten-lookup limit once includes are followed, including 54 running p=reject on an SPF record receivers cannot finish evaluating.

We resolved the DMARC and SPF records of the Tranco top 10,000 domains on 15 August 2026, then did something most adoption surveys skip: we followed every include: and redirect= to the bottom and counted the DNS lookups a receiving mail server actually performs.

That second step changes the answer completely. Read off the record, only 3 domains appear to break RFC 7208’s ten-lookup limit. Expanded properly, 171 do — and 168 of those have records that look entirely reasonable to anyone reading them.

Those 168 domains have SPF that does not work. Not “works imperfectly”: a receiver that hits the limit returns permerror and stops evaluating. The record is there, it parses, it looks maintained, and it is not protecting anything.

Why the record looks fine

RFC 7208 limits SPF to ten DNS lookups during evaluation, not ten terms in the record. Every include: pulls in another record whose own mechanisms also count, and those can include further records.

So a domain can publish four includes — visibly well under the limit — and blow past it through providers nested two and three levels down. Nobody wrote a bad record. Each team added one legitimate vendor, and the total crossed a line nothing warns them about.

Here is gitlab.com, at 13 lookups:

include:mail.zendesk.com
include:_spf.google.com
include:mktomail.com
include:_spf.salesforce.com
  exists (+1)
include:_spf-ip.gitlab.com
a (+1)
include:mailgun.org
  include:_spf.mailgun.org
    include:_spf1.mailgun.org
    include:_spf2.mailgun.org
  include:_spf.eu.mailgun.org
include:_spf.sendergen.com

Support tooling, Google Workspace, marketing automation, CRM, transactional mail. Every entry is a real business need. Mailgun alone contributes four lookups because its record nests three more inside itself — that cost is invisible unless you go looking.

who.int reaches 17 the same way, accreting Outlook, an inter-agency relay, Swisscom, Salesforce, Oracle Taleo and Zoho over what was presumably years. ebay.co.uk reaches 11 through nested internal includes plus Emarsys, SendGrid and Khoros.

None of these are careless. They are what happens when a domain outlives the number of SaaS vendors its SPF record was designed for.

The part that actually matters

Failing SPF is recoverable if something else is holding the line. For 54 domains in our sample, nothing is.

54 domains publish p=reject on top of an SPF record receivers cannot finish evaluating — including who.int, gitlab.com, ebay.co.uk, applovin.com, dyndns.org and branch.io.

p=reject is the strictest DMARC policy: it instructs receivers to discard mail that fails authentication. Running it is a sign of a team that took email security seriously enough to reach the end of the DMARC rollout — which is exactly what makes this combination worth flagging. The strict policy is resting on a foundation that returns permerror.

In practice these domains are leaning entirely on DKIM, whether or not they intended to, because DMARC passes if either SPF or DKIM aligns. If DKIM is solid, mail flows and nobody notices. If a DKIM key rotates badly or a vendor sends unsigned, the SPF safety net that was supposed to be underneath is not there.

It fails quietly, which is the whole problem. There is no bounce, no alert, no dashboard turning red.

Everything else we measured

The headline adoption numbers, for context. These are consistent with what other surveys report, and they are not the interesting part.

DMARC, across 10,000 domains:

StatusDomainsShare
Enforcing (p=quarantine or p=reject)4,99349.9%
Monitoring only (p=none)1,69316.9%
No DMARC record3,26432.6%
Invalid record280.3%
Resolution error220.2%

Of the 6,686 domains publishing a valid record, three-quarters have moved past monitoring: 47.1% at p=reject, 27.6% at p=quarantine, 25.3% still at p=none.

SPF: 7,469 domains (74.7%) publish a record. Of those, 50.9% end in -all (hard fail), 43.7% in ~all (soft fail), 2.1% in ?all (neutral, which asserts nothing). Median evaluation cost is 3 lookups; the 95th percentile is exactly 10, which means roughly one domain in twenty is sitting on the line.

Crossing the two:

PostureDomainsShare
DMARC enforcing and SPF -all2,80328.0%
DMARC enforcing only2,19021.9%
SPF -all only99710.0%
Neither4,01040.1%

Two in five of the world’s most-visited domains have neither an enforcing DMARC policy nor a hard SPF fail.

How we did it, and what we got wrong first

Domain list: Tranco top 1 million, list dated 13 August 2026, top 10,000 taken as the population. Records resolved over Cloudflare DNS-over-HTTPS on 15 August 2026. 9,978 of 10,000 domains resolved; 22 returned errors and are excluded from percentages where noted.

Classification is not a fresh implementation — it is the same logic behind our public DMARC record checker and SPF record checker. If these numbers and those tools disagreed, one of them would be wrong, and we would rather not have to work out which.

We only queried public DNS records through a resolver. Nothing connected to any scanned host, and no mail server was probed.

One correction worth stating plainly, because it changed the result: our first expansion pass deduplicated domains globally, so if two includes both resolved to _spf.google.com, the second was skipped and flagged as a loop. That is wrong. Two includes reaching the same provider form a diamond, not a cycle, and a receiver evaluates and charges for both.

That bug reported 179 include loops where there are 6, and undercounted violations at 127 instead of 171. Cycle detection has to track the ancestor chain of the current branch, not everything visited. We caught it before publishing; we mention it because the same mistake would flatter any tool that makes it, in the direction of telling you your record is fine.

Known limits of this method: we count lookups but do not evaluate macros (%{i} and friends are skipped rather than expanded), we cap runaway chains at 30 lookups rather than resolving them fully, and a handful of domains rate-limited us. All three make our figures a lower bound. The real violation rate is somewhat higher than 2.3%.

Check your own domain

If you run a domain with more than two or three SaaS vendors sending on your behalf, you are closer to this limit than you think. Our SPF record checker and DMARC record checker will tell you where you stand, and both run in your browser.

Three things worth knowing if you find yourself over:

Flattening is a trade, not a fix. Replacing includes with literal IP ranges gets you under the limit and transfers the maintenance burden to you — when a vendor changes infrastructure, your record is now silently wrong instead of automatically correct. Some teams accept that trade deliberately with automation to re-flatten. Doing it by hand once and forgetting is worse than the problem.

Audit before you optimise. Most over-limit records contain vendors nobody has used in years. Removing dead includes is free and reversible; flattening is neither.

Subdomain policy is separate. sp= governs subdomains independently of p=. A domain that tightens p to reject and leaves sp=none in place has protected the apex and left every subdomain open — the same class of problem as this article: a record that reads as strict and has a hole in it.

We publish DMARC on nexkit.uk ourselves, currently at p=none while we collect reports before tightening. It would have been poor form to write this without checking — and when we did, we found we had no record at all. It is there now.


Raw data — 10,000 domains with DMARC and SPF classification and expanded lookup counts — is available on request. If you want your own domain’s chain expanded rather than the summary our checker gives, get in touch and we will run it.