SPF PermError — “too many DNS lookups”: your record broke a hard limit. Here is how to count it, clean it, and stay under it.
First published 20 October 2026 · Lookup costs below were counted from live DNS records on 20 October 2026 — not copied from older guides, several of which are now wrong.
PermError Authentication-Results: spf=permerror
The receiving server could not evaluate your SPF record at all. Not a fail — a broken record.
spf = permerrorDMARC loses a leg
The fast answer
PermError is not a reputation problem and not a passing glitch. It means a receiving server tried to evaluate your SPF record and the record itself is invalid. There are two usual causes, in this order:
Your record needs more than 10 DNS lookups. Every include:, a, mx, exists and redirect in your record costs at least one — and some includes secretly cost 2 or 3, because the record they point to contains further includes. Years of adding one tool at a time is how domains cross the line.
Your domain has two v=spf1 records. One name, one SPF record — two records is an automatic PermError, however perfect each one looks.
The fix is accounting, not magic: count your record term by term (table below), remove the dead weight, and get under 10 with room to spare.
Do not fix the wrong domain first. SPF judges the envelope sender — the Return-Path / smtp.mailfrom domain in the message headers — which is not always the From address your customer sees. If the failing mail is sent by a tool on a subdomain (billing, forms, a helpdesk), the broken record may live on that subdomain, with its own separate budget. In the bounce or in Gmail's Show original, read the smtp.mailfrom domain first, then count that domain's record.
SAMPLE HEADER
Authentication-Results: mx.google.com;
spf=permerror(too many DNS lookups) smtp.mailfrom=yourdomain.com
dkim=pass header.d=yourdomain.com
dmarc=fail header.from=yourdomain.com
Do this: find this block before you change anything — Show original in Gmail, or the bounce's attached headers. spf=permerror is the record confessing it cannot be evaluated; note that DKIM can still pass while DMARC fails on the SPF leg. Sample values; your domain will differ.
Count your own record — the honest price list
Not all includes cost the same. These are the real costs of common entries, counted by resolving each vendor’s live record and its nested includes:
Entry in your record
Cost
Why
include:_spf.google.com (Google Workspace)
1
Google’s record is now flat IP ranges — no nested terms. (Older guides say 4. They are out of date.)
Its record nests a second include (ab.sendgrid.net).
include:secureserver.net (GoDaddy legacy mail)
3
Nests spf-0.secureserver.net, which itself nests Microsoft’s include.
include:shops.shopify.com (Shopify)
1 — for nothing
It resolves to an empty record (v=spf1 ~all) that authorises zero servers. Shopify authenticates by CNAME, not by this include. Pure dead weight — remove it.
a or mx
1 each
Legacy entries from old setups; each costs a term plus its own lookups.
ip4: / ip6: / all
0
These cost no lookup. That is why cleanup often converts lookups into IPs.
Mechanics: RFC 7208 §4.6.4 sets the 10-term limit and lists what counts; §2.6.7 defines PermError; §4.5 makes two records a PermError; §5.5’s heading for ptr is literally “do not use.” Vendor costs verified against live DNS, 20 October 2026.
The budget, drawn. Every block is one term your record spends; the nested entries (orange, red) are the ones nobody suspects.Do this: when you price an include, open the record it points to — and any record that points to. The nesting is where budgets quietly die.
A real-shaped example: 12 terms, and the cleanup to 2
This is the shape of record we see after about six years of “just add this tool”: a leftover a mx, both big platforms, a shop, a newsletter tool, a helpdesk, a transactional trial — and a GoDaddy relic from the first mailbox:
Count it: 1 (a) + 1 (mx) + 1 (Google) + 1 (Microsoft) + 1 (Shopify) + 1 (Mailchimp) + 1 (Zendesk) + 2 (SendGrid) + 3 (GoDaddy) = 12 terms. PermError. And it explains the symptom owners always report — only some mail bounces: evaluation runs left to right, so what fails depends on where the limit is hit on each receiver’s path.
The cleanup asks one question per entry — does this still send as us? The dead platform stays only if it is truly used; the empty Shopify include goes; the GoDaddy relic goes; the newsletter moves to its own subdomain (news.), which gets its own record and its own budget:
(The ip4: above stands in for the store server’s real address — the pattern is what matters: a stable sender becomes an IP, which costs nothing.)
The fix, in safe order
Save the current record before you touch it. Copy the exact TXT value and its TTL into a note. If a cleanup edit misfires, you can restore the old record in one paste instead of reconstructing it from memory while mail is failing.
Rule out the two-records trap first. Look up TXT records on your domain. If two begin with v=spf1, merge them into one record and delete the other. Nothing else matters until this is done.
Write your record out and price every term using the table above. Check any include not listed by looking up its record: if it contains its own include:, add those too.
Remove the dead. Tools you cancelled, trials you never adopted, the previous platform you migrated away from, legacy a/mx from setups nobody remembers, and any include whose target is an empty record. In real records this step alone usually recovers 3–5 terms.
Move bulk senders to a subdomain. Newsletters and marketing platforms belong on a subdomain (for example news. or em.) with their own SPF, DKIM and DMARC. Your root record keeps its budget for the mail your business runs on.
Convert only the stable senders to ip4:. A server you own with a fixed address costs zero lookups as an IP. Do not do this for big platforms — their addresses change, and a stale IP silently breaks that sender.
Your DNS host → Edit the existing TXT record (never add a second one)
Type1TXT
Name2@ — the domain whose mail is failing (check the envelope domain first)
Value3Your cleaned record — every term priced, total under 10 with headroom
TTLLeave as it was — you saved it in step 1
Save
Do this: edit in place, then re-look-up the domain and confirm exactly one v=spf1 record comes back. Field map drawn by Merroway.
About “flattening” services, honestly: they rewrite your includes as IP lists and keep them refreshed. Microsoft documents the technique with cautions — never flatten a Microsoft 365 include, and any flattened record needs periodic review, because a stale snapshot fails quietly. It is a legitimate last resort for a record that cannot fit otherwise, not a first move.
Prove it worked
Re-run the lookup: your domain returns onev=spf1 record, and your hand count is under 10 — with headroom, not at exactly 10.
Send mail from each surviving sender to a Gmail address, open Show original, and confirm SPF PASS — no permerror in the authentication results — and DMARC PASS still holding.
Wait and re-check after DNS propagation before declaring victory; receivers cache at different speeds, which is the last source of “it worked for one customer but not another.”
Do this: send one message from each surviving sender — inbox, forms, helpdesk, billing — and check it reads like this. A fix that only covers the sender you tested is a partial fix. Sample values.
Still failing? Work this ladder in order
Syntax, not size. A trailing dot after a domain in an include, include= instead of include:, or a space after the colon each invalidate the record. Compare character by character against the vendor’s documented string.
An include points at nothing. If a target domain has no SPF record, the include itself is a PermError under the RFC. This happens when a vendor retires a service — another reason dead includes are dangerous, not merely wasteful.
The redirect traps. A redirect is silently ignored if your record also contains an all — a classic “I set it and nothing changed.” And a redirect to a name with no SPF record is, again, a PermError.
You counted 10 and it still fails. The limit counts every term evaluated across nested records, including lookups inside the records your includes point to — and “void” lookups (queries that return nothing) have their own small allowance the RFC says should be capped at two. If your count only works on your own arithmetic, use a checker that draws the full tree, and trust the tree over your count.
Someone keeps re-adding tools. The durable fix is process, not DNS: one named owner of the record, and a rule that no tool gets added without the count being re-done. PermError is almost never a one-time accident; it is what an unowned record becomes.
FAQ — the issues behind this error, answered
Is PermError the same as SPF failing?
Worse. A plain fail means the record worked and the sender was not on it. A PermError means the record could not be evaluated at all — receivers treat the domain as having no usable SPF, and DMARC loses one of its two legs for every message.
Can I just raise the limit?
No. The 10-lookup limit lives in the SPF specification (RFC 7208) and is enforced by the receivers, not by your DNS host. There is no setting on your side that raises it.
Google’s own setup page told me to add the Shopify include. Why do you say remove it?
Because the two vendors’ documents conflict, and live DNS settles it: the include resolves today to an empty record that authorises nothing, and Shopify’s own current documentation says its CNAME authentication handles SPF with no separate SPF record. We follow the live record and the service’s own docs — and we have flagged the disagreement here rather than hiding it.
Sources
RFC 7208 (SPF) §§2.6.7, 4.5, 4.6.4, 5.2, 5.5, 6 · Google Workspace Admin Help — Set up SPF and SPF troubleshooting · Microsoft Learn — Set up SPF for Microsoft 365 · Shopify Help Center — Email rewrites · Vendor include costs counted from live DNS (Google Public DNS) on 20 October 2026. · Method cross-check (envelope-domain diagnosis, backup-before-edit): CyberTek Group.
Verification note: mechanics are quoted from the RFC; every vendor cost in the table was resolved live on the date above, because include costs change when vendors restructure their records — Google’s and Microsoft’s both flattened in recent years, which is exactly why older guides’ numbers are wrong.
Did this fix work for you?
Your reaction tells us whether this guide solved the problem it promised to solve.