The SPF 10-lookup limit: how to count and get back under it
Which SPF mechanisms count toward the 10-lookup limit, how to audit your record by hand, and the safe ways to trim includes before you hit permerror.

On this page(7 sections)
- What the limit actually says
- Terms that count
- Terms that are free
- The two details people miss
- Auditing a record by hand
- Getting back under ten
- 1. Remove what you no longer use
- 2. Drop mx and a when they are redundant
- 3. Move senders to subdomains
- 4. Lean on DKIM for some sources
- 5. Flattening, as a last resort
- The record length limit is a different problem
- Also watch for void lookups
- A checklist for every SPF change
- Key takeaways
Your SPF record can look perfectly valid and still fail on every message, because the receiver gave up counting DNS lookups before it reached your actual sending IP. The fix starts with counting the way the receiver counts.
What the limit actually says
RFC 7208, the SPF specification, caps the number of DNS-querying terms a receiver will evaluate for a single SPF check at 10. When evaluation would need an eleventh, the result is permerror. Under DMARC, a permerror is treated as an SPF failure, and many receivers treat it as a failure outright even without DMARC.
The limit exists to keep SPF from being used as a DNS amplification tool, and to stop one check from chaining through hundreds of nested records. It applies to the whole evaluation tree, not just to the record at the top.
Terms that count
These mechanisms and modifiers each cost one lookup when they are evaluated:
include:aanda:mxandmx:ptr(which you should not use anyway)exists:redirect=
Terms that are free
ip4:andip6:(no DNS query is needed)all- The
v=spf1version tag itself
The two details people miss
First, lookups inside included records count too. An include: costs one, and every counted term inside the record it pulls in also costs one. A single vendor include can quietly spend four or five of your ten.
Second, mx has a hidden cost. The mx mechanism costs one lookup for the MX query, and then the receiver has to resolve the address of each MX host it returns. RFC 7208 limits that to ten MX hosts per mx term; going over is itself a permerror. Those address lookups do not count against the main ten, but they do add latency, and a domain with many MX hosts is a reason to think twice before using mx in SPF at all.
Auditing a record by hand
You do not need a commercial tool to count. Start at the top and walk down with dig:
dig +short TXT example.com | grep spf1
# "v=spf1 include:_spf.mailhost.example include:spf.crm.example mx ip4:192.0.2.10 ~all"
dig +short TXT _spf.mailhost.example
# "v=spf1 include:_netblocks1.mailhost.example include:_netblocks2.mailhost.example ~all"
dig +short TXT spf.crm.example
# "v=spf1 ip4:198.51.100.0/24 a:mail.crm.example ~all"
Now build a small tally as you go:
| Term | Where | Cost | Running total |
|---|---|---|---|
include:_spf.mailhost.example |
top | 1 | 1 |
include:_netblocks1… |
inside mailhost | 1 | 2 |
include:_netblocks2… |
inside mailhost | 1 | 3 |
include:spf.crm.example |
top | 1 | 4 |
a:mail.crm.example |
inside crm | 1 | 5 |
mx |
top | 1 | 6 |
ip4:192.0.2.10 |
top | 0 | 6 |
Six of ten. That looks comfortable until someone adds three more vendors in a quarter, each with nested includes. Re-run this count every time a team asks to "just add one include."
One practical point about evaluation order: SPF stops at the first mechanism that matches. If the sending IP matches an early ip4:, later includes are never queried. That is why a broken record can appear to work in a quick test from one source and fail from another. Always count the worst case, where nothing matches until all.
Getting back under ten
Once you know the total, you have several honest options. They are listed roughly from safest to riskiest.
1. Remove what you no longer use
The most common cause of a blown limit is a vendor you stopped using two years ago. Ask each team which services still send as your domain, check your DMARC aggregate reports for which sources actually appear, and delete includes with no traffic. This costs nothing and has no long-term maintenance burden.
2. Drop mx and a when they are redundant
If your inbound MX hosts never send outbound mail, mx in your SPF record is pure cost. The same goes for a bare a that authorizes your web server, which usually should not be sending mail directly. Replace them with explicit ip4:/ip6: entries for hosts you control and that genuinely send.
3. Move senders to subdomains
SPF is evaluated against the envelope sender (the MAIL FROM, also called the Return-Path), not the visible From header. Most transactional and marketing providers let you set a custom bounce domain such as bounce.example.com. Each subdomain gets its own SPF record with its own ten-lookup budget.
example.com. TXT "v=spf1 include:_spf.workspace.example ~all"
bounce.example.com. TXT "v=spf1 include:spf.sender.example ~all"
news.example.com. TXT "v=spf1 include:spf.marketing.example ~all"
With relaxed DMARC alignment (the default), bounce.example.com aligns with a From address at example.com, so you keep DMARC passing while splitting the budget. This is the most robust long-term fix, and it also separates reputation between mail streams.
4. Lean on DKIM for some sources
DMARC passes if either SPF or DKIM passes and aligns. A vendor that signs with your domain via a DKIM key you publish does not strictly need to be in your SPF record for DMARC purposes. Removing it from SPF is reasonable if you have confirmed its DKIM signatures align, though some receivers still weigh SPF on its own, so do this deliberately rather than by default.
5. Flattening, as a last resort
Flattening replaces includes with the IP ranges they resolve to. It does reduce lookups, but providers change their ranges without telling you, and a stale flattened record fails silently. If you flatten, automate the refresh and monitor it. We cover the trade-offs in detail in a separate post.
The record length limit is a different problem
Do not confuse the lookup limit with length. A single TXT string is limited to 255 characters, but a TXT record can contain multiple strings, which receivers concatenate. A long SPF record split into quoted chunks is fine. A long record with eleven lookups is not. Separately, a DNS response that gets very large may fall back to TCP, which some older resolvers handle badly, so keep records reasonably compact anyway.
Also watch for void lookups
RFC 7208 adds a second, smaller limit: no more than two "void" lookups, meaning queries that return NXDOMAIN or an empty answer. An include pointing at a vendor domain that no longer publishes SPF burns a void lookup every time. Two of those and the check fails, regardless of your total count. Dead includes are therefore doubly worth removing.
A checklist for every SPF change
- Count lookups recursively, assuming nothing matches before
all. - Keep the total at eight or below so the next vendor has room.
- Confirm there is exactly one
v=spf1record on the name. - Check for includes that return nothing (void lookups).
- Prefer subdomains for new senders instead of growing the root record.
- Verify the change from a second resolver after the TTL expires.
Key takeaways
- The SPF limit is ten DNS-querying terms across the entire include tree;
ip4,ip6andallare free. - Exceeding it produces
permerror, which DMARC treats as a fail. - Audit by walking includes with
digand tallying, assuming the worst-case path. - The durable fixes are removing unused vendors and moving senders onto their own subdomains, each with its own budget.
- Flattening works only if you automate and monitor it.
Start with Koltrix
Your domain, one inbox, and an API that sends.
A team inbox where AI sorts and drafts (nothing is sent without your click), plus the transactional API and SMTP relay your product sends with. 7 days free, no card.


