Mail failing with SPF PermError and a message about too many DNS lookups? Your SPF record needs more than the 10 DNS lookups the standard permits. The record itself is syntactically fine — it is simply too expensive to evaluate.
The Error
Depending on which system reports it, you will see one of these:
Received-SPF: permerror (protection.outlook.com: domain of example.com used an
invalid SPF record)
spf=permerror (sender IP is 203.0.113.10) smtp.mailfrom=example.com;
SPF permanent error: too many DNS lookups
550 5.7.24 SPF validation error
RFC 7372 assigns 5.7.24 ("SPF validation error") to exactly this situation — it is used in place of the older 5.5.2 for the permerror case. Do not confuse it with 5.7.23 ("SPF validation failed"), which means SPF was evaluated successfully and the sender was not authorised. A permerror means the record could not be evaluated at all.
And in a Microsoft 365 non-delivery report:
The message required too many lookups.
Microsoft documents the alternative wording "The message exceeded the hop count" for the same cause, which is misleading — it sounds like a mail loop but is the SPF lookup limit.
Why This Happens
RFC 7208 §4.6.4 is explicit:
SPF implementations MUST limit the total number of those terms to 10 during SPF evaluation, to avoid unreasonable load on the DNS. If this limit is exceeded, the implementation MUST return "permerror".
The trap is that lookups are counted recursively. When you publish include:_spf.example-vendor.com, that costs one lookup — plus every lookup inside that record, plus everything inside those. A record with five of your own include: mechanisms routinely resolves to fifteen or more real lookups.
What counts, and what does not
| Costs a lookup | Free |
|---|---|
include: | ip4: |
a | ip6: |
mx | all |
ptr (deprecated — avoid) | exp= |
exists: | |
redirect= |
Two further limits from the same section catch people out:
- Evaluating a single
mxmechanism must not query more than 10 address records, or that mechanism alone returns permerror. - Void lookups — queries returning an empty answer or NXDOMAIN — "SHOULD" be limited to two. Exceeding that also produces permerror. A stale
include:pointing at a decommissioned vendor is a void lookup, so removing dead includes fixes two problems at once.
A second, unrelated cause of permerror is worth ruling out first: publishing two SPF records. A domain must have exactly one. Two produces permerror regardless of lookup count.
Fix 1: Count Your Actual Lookups
Do not guess from the number of include: mechanisms you can see. Resolve the whole tree:
# Your record
dig +short TXT example.com | grep spf1
# Then resolve every include it references
dig +short TXT _spf.google.com
dig +short TXT spf.protection.outlook.com
# ...and anything those records include, recursively
Our DNS lookup tool will resolve the TXT records for you if you do not have dig to hand. Count every include, a, mx, ptr, exists and redirect found anywhere in the tree. If the total is 11 or more, that is your bug.
Note one subtlety that makes this intermittent: receivers evaluate mechanisms left to right and stop as soon as the sending IP matches. A record that would exceed 10 lookups if fully evaluated may still pass for senders listed early in the record. That is why "it works from our mail server but fails from the CRM" is such a common symptom.
Fix 2: Remove Services That No Longer Send
Almost every overweight SPF record contains includes for a vendor the business stopped using two years ago. Go through the record mechanism by mechanism and ask whether that service still sends mail as your domain. Deleting one dead include: that itself contained three nested includes recovers four lookups instantly.
This is the safest fix, so do it before anything clever.
Fix 3: Move Third-Party Senders to Subdomains
Each domain and subdomain gets its own independent 10-lookup budget. This is the structural fix and the one Microsoft recommends.
Send marketing mail from marketing.example.com, ticketing from support.example.com, and application notifications from mail.example.com, each with its own SPF record:
; example.com - staff mail only
v=spf1 include:spf.protection.outlook.com -all
; marketing.example.com - bulk platform only
v=spf1 include:servers.bulkvendor.example -all
This has a second benefit beyond the lookup budget: a marketing campaign's complaint rate can no longer damage the sending reputation of your password reset emails.
Fix 4: Replace Stable Vendors with IP Mechanisms
ip4: and ip6: mechanisms cost zero lookups. Replacing an include: with the addresses it resolves to — known as SPF flattening — is legitimate when the vendor publishes a stable, documented range:
; Before: include:mail.legacyvendor.example (3 nested lookups)
; After:
v=spf1 ip4:198.51.100.0/24 include:spf.protection.outlook.com -all
Flattening is a maintenance commitment, not a one-time fix. If the vendor changes IPs and you do not notice, their mail starts failing SPF silently. Document what you replaced, review at least quarterly, and only flatten vendors that publish their ranges.
Do not flatten Microsoft 365. Microsoft explicitly advises against flattening include:spf.protection.outlook.com because its sending infrastructure uses dynamic addresses that change frequently. The same applies to Google Workspace and any other large cloud sender.
What Not To Do
Two "fixes" circulate widely and both make your domain less safe:
+allauthorises every host on the internet to send as your domain. It does not resolve a lookup-count permerror — the record is still parsed — and it hands spoofers a free pass while destroying any value DMARC could give you. Never publish it.- Deleting the SPF record entirely to make the error go away removes the authentication signal that receivers and your own DMARC policy depend on. The error stops; the deliverability problem gets worse.
Fix the lookup count. There is no shortcut that preserves the protection.
Verify the Fix
Re-resolve and re-count first:
dig +short TXT example.com | grep spf1
Then send a live test to a mailbox you control and read the headers. In Gmail, open the message, choose Show original; in Outlook on the web, open View message details. You want:
spf=pass (sender IP is 203.0.113.10) smtp.mailfrom=example.com
Paste the full header block into our email header analyzer to see the parsed SPF, DKIM and DMARC results together, which is faster than reading raw headers.
Test from every sending system, not just your mail server. Because evaluation stops at the first match, one system passing proves nothing about the others.
Prevention
- Treat the SPF record as change-controlled. Every time someone signs up for a service that sends as your domain, the record needs review — this is the single most common regression.
- Keep a comment in your DNS documentation recording which mechanism belongs to which vendor and who owns it. Records become unmaintainable when nobody remembers what
include:number six is for. - Default new third-party senders onto a subdomain rather than the primary domain.
- Publish DMARC with
rua=reporting and read the aggregate reports. They surface sending sources you had forgotten about before those sources cause a bounce. Build the record with our DMARC generator. - Re-count lookups after any vendor migration; providers change their own nested includes without telling you.
Summary
- Confirm the cause: permerror plus "too many lookups" or "exceeded the hop count"
- Rule out two SPF records — a domain must publish exactly one
- Count recursively, including nested includes, up to the limit of 10
- Delete dead includes first, then move third-party senders to subdomains
- Flatten only stable vendors, never Microsoft 365 or Google Workspace
- Never use
+all— it disables the protection rather than fixing the error