17 May 2026 · 11 min read
SPF is one of the oldest and simplest email-authentication mechanisms still in daily use, and yet it has a quiet trap built into the specification that catches out a surprising number of well-run domains. The trap is a hard ceiling on how much DNS work a receiving mail server is allowed to do while evaluating your SPF record. Exceed it, and your SPF does not just degrade gracefully. It fails outright, and that failure can drag your DMARC result down with it.
This article explains that ceiling in full: the 10 DNS-lookup limit, the separate void-lookup limit that sits alongside it, exactly which SPF mechanisms consume lookups and which do not, how nested include chains quietly eat your budget, and what the resulting permerror actually means for deliverability. By the end you should be able to look at any SPF record and reason about whether it is safe, close to the edge, or already over the line. If you want to check a real domain as you read, the free SPF checker counts the lookups for you and shows the full resolution tree.
What the limit actually is
The relevant rule comes from the SPF specification, RFC 7208, in the section on processing limits. It says that when a receiver evaluates an SPF record, the total number of DNS-querying mechanisms and modifiers it has to follow must not exceed 10. This count includes the lookups triggered by every include, every a, every mx, every ptr, the exists mechanism, and the redirect modifier. The initial lookup that fetches your domain's own TXT record does not count toward the 10; the budget is about the work that record then sets in motion.
Crucially, the count is not the number of mechanisms you wrote in your published record. It is the number of DNS lookups required to fully evaluate it, recursively, including every record reached through an include. A single include that points to a provider whose own SPF record contains four more includes does not cost you one lookup. It costs you one for the include itself, plus everything inside it.
If a receiver follows your record and the count of DNS-querying terms goes past 10, evaluation stops and SPF returns a permerror (permanent error). A permerror is not a soft ~all or a hard -all. It is the SPF engine telling the world that your record is broken and cannot be evaluated. We will come back to what receivers do with that result, because it is the part that actually hurts.
There is one more number worth committing to memory. Within that overall budget, the mx mechanism is itself limited: if an mx lookup returns more than 10 MX records, that is also a permerror. And the ptr mechanism is capped at resolving 10 names. But the headline rule, the one that breaks most real-world records, is the flat ceiling of 10 DNS-querying terms total.
Which mechanisms cost a lookup, and which do not
This is where most people go wrong, because the SPF syntax does not visually distinguish the expensive mechanisms from the free ones. Here is the honest breakdown.
The mechanisms that cost one DNS lookup each are:
include:fetches and evaluates another domain's SPF record. This is the big one, and the most common source of bloat.alooks up the A or AAAA records for a domain.mxlooks up the MX records for a domain, then resolves each of those names.ptrdoes a reverse DNS lookup. This is discouraged by the specification and you should generally not use it at all.exists:performs a DNS query to test whether a name resolves.redirect=is a modifier, not a mechanism, but it triggers a lookup of the target domain's SPF record and counts the same way.
The terms that cost nothing, because they are evaluated entirely from the record text without any further DNS query, are:
ip4:is a literal IPv4 address or CIDR range.ip6:is a literal IPv6 address or range.allis the catch-all at the end (-all,~all,?all,+all).v=spf1is the version tag.
That last group is the key to fixing an over-limit record. Every address you can express as a literal ip4: or ip6: is an address that costs you zero lookups. Every provider you express as an include: may cost you several. This asymmetry is the entire reason SPF flattening exists, which we will get to.
A worked example makes the counting concrete. Take this record:
v=spf1 a mx include:_spf.google.com include:sendgrid.net include:servers.mcsv.net -all
At first glance that is four mechanisms plus -all. But count the actual DNS work:
ais 1 lookup.mxis 1 lookup (plus more if there are many MX hosts, but assume one here).include:_spf.google.comis 1 lookup for the include itself, and that record then expands into several more includes of its own, typically pulling the running total up by three or four.include:sendgrid.netis 1, and it nests further.include:servers.mcsv.netis 1, and Mailchimp's record also nests.
By the time every nested include is resolved, a record that looked like five terms can easily resolve to 12 or 14 lookups. It was under the limit on paper and over the limit in reality.
How includes consume the budget
The include mechanism is the heart of the problem because it is recursive and invisible. When you write include:_spf.google.com, you are not adding one entry to a list. You are inlining the entire contents of Google's SPF record at that point, and whatever Google's record includes gets inlined too, all the way down.
You can see this for yourself. Google's _spf.google.com does not contain IP addresses directly. It contains further includes such as _netblocks.google.com, _netblocks2.google.com and _netblocks3.google.com. So a single line in your record, include:_spf.google.com, actually spends one lookup on _spf.google.com and then three more on the netblock records it references. That is four lookups from one line you wrote.
This is why two domains with visually identical-looking SPF records can have wildly different lookup counts. The cost is determined by the providers you include, and providers change their records without telling you. A vendor that fit comfortably in your budget last year may have added a new include this year, silently pushing you from 9 lookups to 11 and breaking SPF for a domain that has not changed a single character of its own record. This is one of the strongest arguments for ongoing monitoring rather than a one-time setup: the failure can appear with no action on your part.
To reason about a record, expand it as a tree. Each branch is one lookup, and you add up the branches:
yourdomain.com
├── a (1)
├── mx (1)
├── include:_spf.google.com (1)
│ ├── include:_netblocks.google.com (1)
│ ├── include:_netblocks2.google.com (1)
│ └── include:_netblocks3.google.com (1)
├── include:sendgrid.net (1)
└── include:mailgun.org (1)
└── include:mailgun.org... (1)
The total is the count of every node that triggered a query, not the count of top-level lines. The SPF checker renders exactly this tree for any domain and gives you the running total, which is far quicker than tracing it by hand.
The void-lookup limit: the second, quieter ceiling
The 10-lookup limit gets all the attention, but RFC 7208 defines a second, separate constraint that catches subtler problems: the void-lookup limit.
A void lookup is a DNS query made during SPF evaluation that returns either an empty answer (no records of the type asked for) or an NXDOMAIN (the name does not exist at all). The specification says that if the number of void lookups exceeds two during evaluation, the receiver should return a permerror, just as if you had blown the main limit.
The purpose of this rule is to stop SPF being abused as a denial-of-service amplifier and to penalise badly maintained records. In practice, it bites domains in a few recurring ways:
- An
include:that points at a provider you no longer use, where the target domain has been decommissioned and now returnsNXDOMAIN. That is one void lookup. A couple of dead includes and you are over the void limit even though your overall lookup count is healthy. - An
aormxmechanism for a hostname that no longer has the relevant records, returning an empty answer. - A typo in an
includetarget.include:_spf.gooogle.comdoes not error loudly; it just becomes a void lookup that quietly erodes your void budget.
The void-lookup limit is worth knowing precisely because it is invisible in the happy path. A record can pass for years, and then a third party retires a domain you were including, and suddenly your SPF starts returning permerror for a reason that has nothing to do with how many providers you have. When you audit an SPF record, check not only that the total stays under 10 but that every lookup actually resolves to something. A good SPF checker flags void and dead lookups specifically.
What a permerror does to your mail
It is tempting to treat a permerror as a minor configuration warning. It is not. Here is what actually happens downstream.
First, a permerror means SPF provides no positive authentication for the message. The receiver could not determine whether the sending IP was authorised, so SPF contributes nothing in your favour. For a plain SPF check, many receivers treat permerror as roughly equivalent to a fail or as a strong negative spam signal. Mail is more likely to be filtered or rejected.
Second, and more importantly for most modern setups, a permerror interacts badly with DMARC. DMARC passes if either SPF or DKIM produces an aligned pass. If your SPF is throwing a permerror, the SPF half of that equation is gone. You are now relying entirely on DKIM to carry DMARC. If your DKIM signing is also imperfect, perhaps a forwarded message stripped the signature, or a particular sending platform was never set up to sign, then DMARC fails, and at p=quarantine or p=reject that legitimate mail gets quarantined or thrown away.
So the real-world chain of damage is: too many includes, leads to permerror, leads to SPF no longer helping DMARC, leads to over-reliance on DKIM, leads to legitimate mail failing DMARC the moment one DKIM signature is missing. A domain can look perfectly configured and still lose mail because the SPF lookup count crept over 10. We cover the failure mode in depth in SPF permerror and the 10-lookup limit.
You can confirm whether this is happening to you by reading your aggregate reports. A source that shows DKIM pass but SPF permerror, or SPF fail with no DKIM, is exactly the pattern this limit produces. The DMARC report analyzer surfaces per-source SPF and DKIM results so you can spot the senders that have quietly fallen over.
How to get back under the limit
The good news is that an over-limit SPF record is a solvable engineering problem, not a fundamental constraint on how many services you can use. There are four practical strategies, roughly in order of preference.
1. Remove senders you no longer use
The cheapest lookup is the one you delete. Audit every include and ask whether you still send mail through that provider. Trial platforms, old marketing tools, a CRM you migrated away from: these leave includes behind, and each one is a lookup you are paying for and a candidate for the void-lookup limit too. This step alone often brings a record back under budget with no clever techniques required.
2. Replace includes with literal IP ranges where you safely can
If a provider sends only from a small, stable set of IP addresses that they publish and commit to, you can sometimes replace include:provider.net with the underlying ip4: and ip6: ranges directly. Literal IPs cost zero lookups. The serious caveat is that you are now responsible for keeping those ranges current; if the provider changes their IPs and you have hard-coded them, your mail from that provider will fail. Only do this for providers whose ranges are genuinely stable, and document it.
3. Flatten the record
SPF flattening is the systematic version of strategy two. A flattening service resolves every include in your record down to the underlying IP ranges and publishes a single SPF record made almost entirely of ip4: and ip6: literals, with the nested-include lookups eliminated. Because literals cost nothing, a flattened record can represent dozens of senders while consuming a tiny fraction of the lookup budget. The catch, again, is freshness: a flattened record is a snapshot, so it has to be re-flattened automatically whenever an underlying provider changes their IPs, or it will silently start dropping legitimate sources. We explain the mechanics, and the freshness trap, in how SPF flattening works. The hosted SPF service does this flattening and keeps it current for you, so the record stays valid without manual maintenance.
4. Split sending across subdomains
SPF is evaluated against the domain in the envelope-from (return-path), not necessarily your main domain. If you send bulk marketing from mail.example.com and transactional mail from notify.example.com, each subdomain has its own SPF record with its own independent 10-lookup budget. Segmenting senders by subdomain is a clean way to spread the load, and it has the side benefit of isolating a marketing platform's reputation from your core domain. It does require that each platform sends with the right return-path domain, which not all of them make easy to configure.
A quick checklist for a healthy SPF record
When you review an SPF record, run through this short list:
- Count the real DNS lookups, fully expanded through every nested include, and confirm the total is comfortably below 10. Aim for headroom, not exactly 10, because providers add includes over time.
- Confirm every lookup actually resolves. No dead includes, no typos, no decommissioned provider domains. Keep void lookups at zero where you can, and never above two.
- Remove any sender you no longer use.
- Make sure there is exactly one SPF record (one TXT record beginning
v=spf1) on the domain. Two SPF records is itself a permerror, separate from the lookup limit. - End with
-all(hard fail) or~all(soft fail) deliberately, not?allor+all, which weaken the record's meaning. - If you flatten or hard-code IPs, make sure something keeps them fresh automatically.
A record that passes all six is one that will keep helping your DMARC result rather than quietly undermining it.
The practical takeaway
The SPF 10-lookup limit is not a quirk you can ignore. It is a hard processing ceiling that turns a record with too many nested includes into a permerror, and a permerror removes SPF from your DMARC equation entirely, which is how a domain that looks correct ends up losing legitimate mail. The void-lookup limit sits alongside it and punishes dead or mistyped includes in the same way. The fixes are well understood: prune unused senders, prefer literal IPs and flattening for stable providers, and split heavy senders across subdomains so each gets its own budget.
The fastest way to know where you stand is to measure. Run your domain through the free SPF checker, which expands every include and counts the lookups for you, and pair it with the DMARC report analyzer to see whether any real senders are already failing because of it. If you would rather not police the lookup count by hand every time a provider changes their record, the hosted SPF service flattens and maintains a valid record for you, and the wider done-for-you platform keeps SPF, DKIM and DMARC aligned all the way to p=reject without an outage.