SPF Permerror: Fix 'Too Many DNS Lookups' Once and for All
Your newsletter tool says the send worked. Half your list never got it, and the ones that did found it in spam with a big red "this sender could not be verified." A quick check at mail-tester.com shows it: permerror on your SPF record. Not "missing SPF" - your SPF record exists, and that's exactly the problem. It's gotten too big.
Symptom: SPF Shows "Permerror" or "Too Many DNS Lookups"
You'll usually spot this one of a few ways:
- A bounce-back or spam-report header showing
spf=permerrorinstead ofpassorfail - Tools like MXToolbox or mail-tester.com flagging "SPF Permanent Error: Too many DNS lookups"
- Mail that used to deliver fine suddenly starts landing in spam after you connect a new sending tool (a CRM, a helpdesk, a marketing platform)
- Google Postmaster Tools or your DMARC aggregate reports showing SPF failing intermittently, not consistently - a classic sign you're right at the edge of the limit
The frustrating part: the record isn't broken syntactically. Every include and every IP range in it is valid on its own. It just adds up to more DNS lookups than the spec allows, and receiving mail servers are required to treat that as a hard failure - not a warning.
Cause: SPF's 10-Lookup Ceiling
RFC 7208 caps SPF evaluation at 10 DNS lookups per check. Once a receiving server crosses that limit while resolving your record, it stops and returns permerror - and most receivers then treat that the same as a straight-up SPF fail. This isn't a Gmail quirk or a Getwebup limit; it's baked into how the protocol was designed to stop SPF checks from becoming their own DoS vector.
The mechanisms that count toward the 10:
| Mechanism | Counts as a lookup? | Notes |
|---|---|---|
include: | Yes | Plus every lookup inside the included record, recursively |
a | Yes | One lookup per domain referenced |
mx | Yes | One lookup for the MX record, plus one per MX host it resolves to |
ptr | Yes | Deprecated - avoid entirely, it's slow and unreliable |
exists: | Yes | Often used in shared-hosting SPF setups |
redirect= | Yes | Replaces the whole evaluation with another domain's record |
ip4: / ip6: | No | Free - raw IP ranges never cost a lookup |
all | No | Terminal qualifier, no lookup needed |
The trap is that include: is recursive. Your record might have three includes, but if each of those points to a provider that itself includes two more domains, you're not at 3 - you could easily be at 9 or 10 before you've added your own mail server. Add one more marketing tool and you're over.
Step 1: Actually Count Your Lookups
Don't guess - count. Pull your current record first:
dig TXT yourdomain.com +short
You'll get something like:
"v=spf1 include:_spf.google.com include:sendgrid.net include:mailchimp.com include:secureserver.net ~all"
Now walk each include manually - dig TXT _spf.google.com +short, then dig TXT on whatever that resolves to, and so on - or save yourself the manual recursion and run the record through a free SPF lookup counter (MXToolbox's SPF check tool shows the full expanded tree and the exact count). Either way, get the real number before you touch anything. It's common to find you're already at 11 or 12 without realizing it.
Step 2: Find What's Actually Bloating the Record
Once you see the expanded tree, the usual culprits are:
- Dead includes. An ESP you stopped using two years ago, still sitting in the record, still costing lookups on every single check.
- Duplicate coverage. Your hosting provider's include and your registrar's default include both authorizing the same sending IPs.
- Chained third-party includes. Some transactional-email or marketing tools route their own SPF through two or three layers of their own infrastructure providers, each one a lookup you don't control.
- Using
aormxwhen a directip4:would do. If your mail server's IP never changes,ip4:203.0.113.10does the same job asmxfor zero lookup cost.
Cut anything you don't recognize or no longer use before you optimize what's left - it's the fastest way to claw back headroom.
Step 3: Flatten the Record
"Flattening" means replacing a lookup-costing mechanism with the raw IP ranges it resolves to, so the receiving server doesn't have to chase it down. Take an include that only resolves to a couple of fixed IP blocks and inline those directly:
Before:
v=spf1 include:mail.oldvendor.com ~all
After:
v=spf1 ip4:198.51.100.0/24 ip4:198.51.100.128/25 ~all
This drops that entry from 1+ lookups to zero. The catch: you now own keeping those IPs current. If the vendor rotates their sending infrastructure and you don't update the flattened ranges, your mail starts failing SPF for the opposite reason - unauthorized sender. Flatten vendors with stable, published IP ranges (most transactional email providers publish these). For anything that changes its sending IPs frequently, keep it as an include: and instead cut the fat elsewhere in the record.
If you're managing a record with genuinely many senders and don't want to hand-maintain IP lists, an SPF-flattening service (there are several free and paid ones) will auto-refresh a flattened record for you on a schedule. Worth it once you're juggling five or more senders; overkill for two or three.
Step 4: Publish and Verify
Update the TXT record at the root of your domain - in cPanel that's Zone Editor → Manage on the @ host. Remember only one SPF record is allowed per domain; if you see two v=spf1 TXT entries, merge them into one or the record fails outright regardless of lookup count.
After publishing, don't just assume it worked:
- Re-run the SPF lookup counter and confirm you're now under 10, ideally with a few lookups of headroom for the next tool someone adds
- Send a test message to mail-tester.com and confirm SPF shows
pass, notpermerrororsoftfail - Check DMARC aggregate reports over the next few days if you have DMARC set up - they'll show real-world pass/fail rates across every sender, not just your test
DNS changes for a TXT record are usually visible within 30-60 minutes, but give it a few hours before concluding a fix didn't take.
Prevention: Keep Headroom in the Record
| Practice | Why it matters |
|---|---|
| Audit the SPF record whenever you add or drop a sending tool | Bloat happens one "just add this include" at a time, never all at once |
Prefer ip4:/ip6: over a/mx for your own mail server | Free lookups, and your server IP rarely changes anyway |
Never use ptr | It's deprecated, slow, and burns a lookup for something SPF no longer needs |
| Keep a running list of what each include is for | Makes it obvious which ones are safe to remove later |
| Aim to stay at 6-7 lookups, not exactly 10 | Leaves room for the next marketing or helpdesk tool without another emergency fix |
SPF's 10-lookup limit isn't going away, and it's not negotiable - it's enforced by every major receiver, not configurable on your end. The fix isn't clever DNS tricks, it's just keeping the record honest: only what's currently sending mail on your behalf, nothing left over from tools you stopped using two migrations ago.
Frequently asked questions
How do I know how many DNS lookups my SPF record is actually using?
Run your domain through an SPF lookup checker (MXToolbox's SPF record check is a common free one) - it expands every include recursively and shows the exact count. Manually running dig TXT on each include and its nested includes works too, but it's easy to undercount by hand since includes can be several layers deep.
Can I just add more IP addresses instead of using include: to avoid the lookup limit?
Yes - ip4: and ip6: mechanisms never count against the 10-lookup limit, only include, a, mx, ptr, exists, and redirect do. Flattening a stable include into its raw IP ranges is exactly how you claw back lookups, but you're then responsible for updating those ranges if the sender's infrastructure changes.
Will fixing SPF alone stop my mail from going to spam?
It removes one specific failure, but SPF is only one of three checks. If DKIM isn't signing your outgoing mail or DMARC isn't published, receivers can still flag messages as suspicious. Check all three together, not just the one that's currently throwing an error.
I fixed the record but mail-tester.com still shows the old error - why?
DNS TXT record changes typically propagate within 30-60 minutes, but caching resolvers and some receivers hold onto old records longer. Wait a couple of hours, then re-run dig TXT yourdomain.com directly against a public resolver like 8.8.8.8 to confirm the new record is actually live before re-testing.
Does void lookups count separately from the 10-lookup limit?
Yes - RFC 7208 also caps 'void lookups' (a lookup that returns no data or NXDOMAIN) at 2. Stale includes pointing at domains that no longer exist can trip this limit even if your total lookup count is otherwise under 10, so remove dead includes rather than leaving them in place.