The short answer: RFC 7208 limits an SPF check to 10 DNS-querying terms (include, a, mx, ptr, exists, redirect), counted across every nested record. Go over and the result is permerror, so SPF fails for every message. Fix it by deleting unused senders, replacing a/mx with ip4:, moving senders to subdomains, or flattening carefully. Check your count with the SPF checker.
Receiving servers evaluate SPF in real time, so the specification caps how much DNS work a single check can cause. Cross that cap and the whole record is thrown away. It is one of the most common SPF faults on domains that have accumulated a CRM, a newsletter tool, a helpdesk and a billing system over the years.
This guide covers exactly what the limit is, how to count lookups by hand (with a worked example), and the four ways to get back under it, including the trade-offs of flattening. If you are new to the syntax itself, start with what an SPF record is and the SPF record syntax guide.
What the Limit Actually Is (RFC 7208, Section 4.6.4)
Section 4.6.4 of RFC 7208 sets two separate limits on DNS activity during one SPF evaluation:
- No more than 10 terms that cause DNS lookups. The terms that count are the mechanisms
include,a,mx,ptrandexists, plus theredirectmodifier. When the limit is exceeded, the check returnspermerror. - No more than 2 "void" lookups. A void lookup is a DNS query that returns no records (an empty answer or NXDOMAIN). Receivers should return
permerrorafter more than two. A typo such asinclude:_spf.gogle.comor ana:entry for a hostname that no longer exists produces a void lookup.
Two related caps exist inside individual mechanisms. An mx mechanism may only use the first 10 MX hostnames it finds, and ptr is limited to 10 names. Those inner address lookups do not count towards the 10, but an mx term itself does.
The limit applies to the whole evaluation, not to your top-level record. Every include: pulls in another domain's record, and every lookup term inside it counts against the same budget of 10. You do not control how many terms your vendors publish, which is why the budget disappears faster than the record looks.
What Counts and What Doesn't
| Term | Counts as a lookup? | Notes |
|---|---|---|
include:domain |
Yes (1) | Plus every lookup term inside the included record |
a, a:domain |
Yes (1) | One query for A/AAAA |
mx, mx:domain |
Yes (1) | The MX query counts once; resolving each MX host does not add to the 10 |
ptr |
Yes (1) | Deprecated by the RFC. Remove it |
exists:domain |
Yes (1) | Typically used with macros |
redirect=domain |
Yes (1) | Plus the lookups in the target record |
ip4:, ip6: |
No | Literal addresses need no DNS |
all |
No | Terminal mechanism |
exp= |
No | Explanation text only |
v=spf1 |
No | Version tag |
The practical rule: every include: costs at least one, and costs more if the vendor's record contains its own include:, a or mx terms.
A Worked Example: Counting Lookups by Hand
Here is a realistic top-level record for a company with a mailbox provider, a newsletter tool, a helpdesk and its own mail server:
v=spf1 mx include:_spf.google.com include:spf.newsletter-vendor.example include:spf.helpdesk-vendor.example a:relay.yourdomain.com -all
Now resolve each term. The newsletter-vendor and helpdesk-vendor records below are hypothetical, but their shape is common:
| Step | Term | Where it resolves | Lookups | Running total |
|---|---|---|---|---|
| 1 | mx |
Your MX records | 1 | 1 |
| 2 | include:_spf.google.com |
Google's record | 1 | 2 |
| 3 | (inside Google) include:_netblocks.google.com |
Google netblock record | 1 | 3 |
| 4 | (inside Google) include:_netblocks2.google.com |
Google netblock record | 1 | 4 |
| 5 | (inside Google) include:_netblocks3.google.com |
Google netblock record | 1 | 5 |
| 6 | include:spf.newsletter-vendor.example |
Vendor record | 1 | 6 |
| 7 | (inside vendor) include:spf2.newsletter-vendor.example |
Vendor sub-record | 1 | 7 |
| 8 | include:spf.helpdesk-vendor.example |
Vendor record | 1 | 8 |
| 9 | (inside helpdesk) a:mail.helpdesk-vendor.example |
A record | 1 | 9 |
| 10 | a:relay.yourdomain.com |
Your A record | 1 | 10 |
That record is at exactly 10 and still valid. The next time the helpdesk vendor adds a nested include:, you are at 11 and every message from your domain returns permerror, even though nothing in your own DNS changed. This is the failure mode to remember: your record can break without you touching it.
The Google structure above (a top-level include that nests three _netblocks includes, four lookups in total) has been stable for years, but vendors do restructure. Treat any figure here as a snapshot and let a checker count the live chain. Microsoft 365's spf.protection.outlook.com has historically been a single lookup because it publishes only IP ranges.
You can also test a domain from the command line:
nslookup -type=TXT _spf.google.com
Read the output, find every include:, a, mx, exists and redirect, and repeat for each include target. The SPF checker does this recursion automatically and flags void lookups too.
What Happens When You Exceed the Limit
The evaluation stops and returns permerror. In message headers it appears like this:
Authentication-Results: mx.example.net;
spf=permerror (example.net: permanent error in processing during lookup of
bounce@yourdomain.com: too many DNS lookups) smtp.mailfrom=bounce@yourdomain.com
The exact wording varies by receiver. The consequences are the same everywhere:
- SPF does not pass for any sender on the domain, legitimate or not.
- Some receivers treat
permerroras a failure; others treat it like no SPF result at all. You cannot rely on the lenient behaviour. - If your DMARC policy is
quarantineorreject, mail that depends on SPF for alignment is filtered or blocked. Mail with an aligned DKIM signature still passes DMARC, which is why DKIM is your safety net. - It hits transactional mail (invoices, password resets) as hard as bulk mail.
If you are already seeing placement problems, work through why your emails are going to spam alongside this guide.
How to Fix It: Four Options, Cheapest First
1. Delete what you no longer use
Compare every include: to the tools actually sending today. Old CRMs, trial accounts and migrated platforms are the usual offenders. Also remove:
- Duplicate includes
ptr(deprecated and slow)aandmxterms that don't correspond to a server sending mail from your domain- Includes with typos (these cause void lookups)
This costs nothing and often saves two to four lookups.
2. Replace a and mx with literal IPs
If you run your own mail server on a fixed address, ip4:203.0.113.25 costs zero lookups while mx costs one. Only do this when the address is static and under your control.
3. Move senders to subdomains
SPF is evaluated against the domain in the envelope sender (the Return-Path), not the visible From address. When you send newsletters as news@mail.yourdomain.com, only the record at mail.yourdomain.com is checked, so each subdomain gets its own fresh budget of 10:
yourdomain.com. TXT "v=spf1 include:_spf.google.com -all"
mail.yourdomain.com. TXT "v=spf1 include:spf.newsletter-vendor.example -all"
help.yourdomain.com. TXT "v=spf1 include:spf.helpdesk-vendor.example -all"
Many vendors already let you set a custom return-path or bounce domain on a subdomain you control, which gets you this separation without changing what recipients see. It also improves reputation isolation. The third-party senders guide covers the dedicated-subdomain approach in more detail.
4. Flatten the record
Flattening resolves includes to their underlying ip4: and ip6: ranges and publishes those directly:
Before: v=spf1 include:spf.newsletter-vendor.example -all
After: v=spf1 ip4:192.0.2.0/24 ip4:198.51.100.0/25 -all
Literal IPs cost nothing, so flattening is the only option that can keep many senders on one domain. It also carries real risks.
Flattening Trade-Offs
| Concern | What happens |
|---|---|
| Stale IPs | Vendors add and retire sending ranges without notice. A static flattened record silently stops authorising new IPs, and mail from them fails SPF. |
| Record size | A single TXT string is limited to 255 characters, so long records must be split into multiple quoted strings. Very large records can also push DNS responses over the size receivers handle comfortably. |
| Lost vendor control | With include:, the vendor maintains the list. With literals, you do. |
| Dynamic flattening services | Hosted tools re-flatten automatically, but you hand them control of a record that authorises mail for your domain. Check their pricing, security and what happens if the service goes down. |
| Over-authorising | Flattening shared-infrastructure vendors can authorise thousands of addresses you never use. SPF cannot distinguish your tenant from others on the same IPs. |
A sensible rule: flatten only vendors who publish stable, documented IP ranges, never flatten Google or Microsoft, and re-verify on a schedule. Mailbox providers change infrastructure often enough that a manually flattened record is a recurring maintenance task, not a one-off fix.
Do not forget the alternative that removes the pressure altogether: make DKIM carry authentication for third-party senders. If every vendor signs with a DKIM key aligned to your domain, DMARC passes without SPF alignment, and you may not need that vendor's include in your root record at all.
Rebuilding and Verifying the Record
Once you know which senders stay, rebuild from scratch instead of editing the old string, so stale entries do not survive:
- List every active sender (check DMARC aggregate reports for sources you forgot).
- Build the record in the SPF generator, which shows the lookup count as you add providers.
- Publish it as the single TXT record starting
v=spf1at the root. Two SPF records is itself apermerror. - Re-run the SPF checker and confirm the count is 10 or lower with no void lookups. Aim for 8 or fewer to leave headroom for vendor changes.
- Send a test message and confirm
spf=passin the headers (the email header analyzer makes this easy).
For the choice of -all or ~all once the record is clean, see SPF hard fail vs soft fail.
Frequently Asked Questions
Is the limit 10 includes or 10 lookups?
Ten lookups, not ten includes. Each include, a, mx, ptr, exists and redirect counts as one, and so does every such term inside an included record. Three includes can easily consume eight lookups.
What is the difference between a permerror and a softfail?
A softfail (~all) is a valid result meaning the sender is probably not authorised. A permerror means the record itself could not be evaluated, because of too many lookups, a syntax error or multiple SPF records. It is a configuration fault, not a verdict on the sender.
What is a void lookup?
A DNS query that returns an empty answer or NXDOMAIN. RFC 7208 allows receivers to return permerror after more than two. They usually come from typos or decommissioned hostnames left in the record.
Does ip4: count toward the limit?
No. ip4:, ip6: and all never trigger DNS queries, so they cost nothing.
Will DMARC still pass if SPF returns permerror?
Yes, if DKIM passes and the signing domain aligns with your From domain. DMARC needs only one aligned pass, which is why configuring DKIM for every sender matters.
Next Step
Run the SPF checker to see your live lookup count, or get a free email audit covering SPF, DKIM and DMARC together. If your record is tangled and you would rather have it fixed, see the email security setup service.