SPF: too many DNS lookups (the 10-lookup limit, and how to fix it)
SPF caps you at 10 DNS lookups. Past that, receivers fail "permerror" — even when your record is valid. How to count yours and four ways to fix it.
SPF: too many DNS lookups (the 10-lookup limit, and how to fix it)
If your DMARC reports show messages with spf=permerror, the most common root cause is the 10-DNS-lookup limit in RFC 7208. Receivers stop evaluating an SPF record after 10 nested DNS queries and treat the result as an error. Your record is syntactically valid, the include chain is correct, but receivers can't finish reading it.
This article walks through what counts as a lookup, how to count yours, and the four practical ways to get back under the limit.
The limit, plainly
When a receiver evaluates SPF for an incoming message, it walks your record from left to right. Every mechanism that requires a DNS query counts as one lookup. RFC 7208 Section 4.6.4 caps this at 10 total lookups, including all transitive lookups inside include:, redirect=, and exists: chains.
If the count exceeds 10, the receiver MUST stop and return permerror. The result is the same as having no SPF at all — except worse, because DMARC will treat permerror as a failed SPF check, which means messages relying on SPF alignment will fail DMARC.
What counts as a lookup
Each of these mechanisms triggers exactly one lookup against the receiver's resolver:
include:domain.com— fetches thedomain.comSPF record. Counts as one. Then every lookup inside that record is also charged against your 10-cap.aanda:other.com— looks up the A/AAAA records of the domain orother.com.mxandmx:other.com— looks up the MX records, then up to 10 A records for the listed mail servers.exists:%{i}.spamlist.example.com— does an A-record lookup against the constructed name.redirect=domain.com— fetches the redirected SPF record. Same recursive cost asinclude:.ptr— does a reverse DNS lookup. Don't use this — it's been deprecated for a decade and many receivers ignore it. Counts against the limit anyway.
These do not count:
ip4:andip6:— literal IP addresses, no DNS needed.all— the catch-all at the end of your record.
Why the limit exists
SPF resolution is recursive. A single SPF record can pull in another record, which pulls in another, and so on. Without a hard limit, a malicious or buggy record could drag the receiver into hundreds of DNS lookups per incoming message — a denial-of-service vector against the receiver's resolver.
Ten was chosen as the cap. It is generous in 2008 (when RFC 4408 set it) and tight in 2026, when most domains route mail through 5+ third-party services that each ship their own include:.
The typical broken record
Look familiar?
v=spf1 include:_spf.google.com include:mailgun.org include:sendgrid.net include:_spf.salesforce.com include:mktomail.com include:_spf.qualtrics.com include:cust-spf.exacttarget.com -allThat's 7 includes. Each one fetches another record. Let's count what the receiver actually does:
| Include | Lookups inside |
|---|---|
_spf.google.com | 4 (Google chains through _netblocks, _netblocks2, _netblocks3) |
mailgun.org | 2 |
sendgrid.net | 2 (chains through sendgrid.net → sendgrid.com) |
_spf.salesforce.com | 2 |
mktomail.com | 2 |
_spf.qualtrics.com | 2 |
cust-spf.exacttarget.com | 2 |
Total: 7 includes × ~2 each = ~14 lookups. The receiver stops at 10 and returns permerror. SPF authentication fails for every message you send.
Step 1 — Count your current lookups
Tools to count:
dig +short txt example.comGet the raw record, then walk each include: and count what's inside. This is tedious for anything beyond two includes.
Faster: use one of the public SPF analyzers (search "SPF lookup count tool"). They will show you the count and the tree of includes. The cleanest reports break down lookups per include so you can see which third party is the worst offender.
DomainCare's email deliverability check parses your SPF record and surfaces the lookup count on every run. Past 8 lookups it warns; past 10 it alerts.
Step 2 — Fix it
There are four practical strategies. Most domains use a combination of two or three.
Fix 1: Remove unused includes
The cheapest fix. Many SPF records accumulate includes for services the company stopped using two years ago. Audit each include against actual sending sources:
- Look at the last 30 days of DMARC aggregate reports. The
source_ipranges tell you who is actually sending mail under your domain. - Cross-reference each
include:to a real sender in those reports. - Remove anything that doesn't match.
Reclaiming 4–6 lookups by removing dead includes is common.
Fix 2: Replace include: with ip4: / ip6: blocks
Some senders publish their SPF as a ~/-all-terminated record but also publish a stable list of IP ranges in their docs. You can replace their include with the explicit IP blocks. Tradeoff: when the sender adds new infrastructure, your record won't pick it up automatically and you'll have to update manually.
This is a good fix for senders whose IP ranges change rarely (a small ESP with a static range) and a bad fix for cloud-scale senders (Google, Microsoft, Salesforce) who shift IPs constantly.
Fix 3: SPF flattening
A third-party service flattens your include: chain into a single static record made entirely of ip4:/ip6: blocks. Examples: SPFflattening.com, EasyDMARC's flattening service, Mailhardener. The flattening service walks your includes, extracts every IP, publishes them in a record you redirect to.
This works because the maximum-size SPF record (255 bytes per chunk, 4096-byte assembled limit) can hold thousands of IP blocks even if it can't hold many includes.
Tradeoffs:
- Maintenance becomes someone else's job. The flattening service updates the IP list when senders change theirs. You're trusting them to do this reliably.
- Adds a dependency. If the flattening service goes down or stops updating, your SPF goes stale and mail starts failing.
- Costs money. Most flattening services are paid; expect $10–$50/month for one domain.
Fix 4: Subdomain delegation
Send marketing mail from marketing.<your-domain>, transactional from mail.<your-domain>, internal alerts from alerts.<your-domain>. Each subdomain gets its own SPF record with only the includes for senders that use it. Your root domain's SPF can stay short.
This is the cleanest long-term fix. It also has independent benefits — you can apply different DMARC policies to each subdomain (strict for the root, relaxed for marketing during a rollout), and BIMI selectors give you per-subdomain logo control.
The downside is engineering work. Every sending pipeline has to be reconfigured to use the right subdomain, and operational tooling (mail-from rewriting, return-path handling) needs to follow.
What to do right now if you're seeing permerror
- Pull your DMARC aggregate reports. Confirm
spf=permerroris the failure mode (notsoftfailornone). - Run an SPF lookup-count check against your record.
- If the count is 10–13: remove dead includes (Fix 1) and you're probably under the cap.
- If the count is 13+: pick between flattening (Fix 3, fast but vendored) and subdomain delegation (Fix 4, slow but durable). Don't do both simultaneously.
- Re-test 24–48 hours after the change so DNS caches drain.
What this won't fix
- SPF softfails (
~allrejecting a forwarded message). Different problem; the messages are reaching receivers but failing alignment because someone in the middle (a mailing list, a forwarder) modified them. DMARC~all-permissive policies hide this; DMARC atp=quarantineexposes it. - DKIM failures. SPF and DKIM are independent; fixing your lookup count won't help if your DKIM signatures are also broken.
- Lookups inside
mx:mechanisms. If your MX block is large, it has its own subordinate cap (10 hosts permx:); going over that returnspermerrorseparately.
Set up SPF lookup monitoring
DomainCare's email deliverability check parses every SPF record on every check run, walks the include chain, and tracks the lookup count over time. Alerts fire at:
- 8 lookups — warning. You're inside the limit but a single new include will push you over.
- 10 lookups — critical. You're at the cap; receivers may already be failing.
- 11+ lookups —
spf_permerroralert. Receivers are failing; messages are losing SPF alignment.
The 8-lookup warning is the one that matters operationally. It catches the rollout before deliverability degrades.
Related
- How to set up an SPF record — the basics.
- SPF vs DKIM vs DMARC — how the three records interact.
- Email deliverability check reference — what DomainCare's email deliverability check verifies.
Common questions
The lookup count tool says 11, but my mail still works in Gmail. Some receivers are more permissive than the RFC requires. Don't trust it — Gmail's leniency on SPF lookups is not contractual, and the bulk-sender requirements they introduced in 2024 explicitly cite DMARC pass rate, which depends on SPF or DKIM alignment.
Will reducing my SPF record's lookup count improve deliverability? Indirectly. SPF doesn't directly affect inbox placement, but permerror is correlated with worse reputation at major receivers. Fixing the count fixes the reputation drag.
Is it OK to keep ?all instead of ~all or -all to avoid these problems? No. ?all ("neutral") tells receivers your SPF doesn't make a claim about non-listed senders — which means SPF cannot contribute to DMARC alignment. You need ~all (softfail) or -all (hardfail) for SPF to work at all. The lookup count is independent of this.
My provider gave me a single include: and my count is already 8. What now? Some providers (Salesforce Marketing Cloud, certain Microsoft 365 configurations) ship deeply nested SPF records that consume most of your budget. Subdomain delegation (Fix 4) is the only durable fix for these — isolate the heavy sender on its own subdomain.