Here is a failure that survives every obvious check. Your SPF record lists every service you send through. The syntax is valid. A lookup tool shows it published correctly. And receiving servers are failing it.
The cause is a limit most people have never heard of, and the more carefully you have listed your providers, the more likely you are to have hit it.
The ten-lookup limit
When a receiving server evaluates your SPF record, it is allowed a maximum of ten DNS lookups. Exceed that and evaluation stops and returns PermError — a permanent error.
Not every part of a record costs a lookup. The distinction is the whole problem.
These cost a lookup each: include, a, mx, ptr, exists, redirect
These cost nothing: ip4, ip6, all
So a record with twelve ip4 ranges is fine, and a record with eleven include statements is broken.
Why it is worse than it looks
The count is not the number of mechanisms in your record. It is the number of lookups performed while resolving it — including the lookups inside every record you include.
When you write include:_spf.google.com, that is one lookup. But Google’s record then includes three more of its own, each of which is another lookup. One line in your record can cost four.
This is why the limit arrives so much sooner than expected. A common, entirely reasonable setup:
v=spf1 include:_spf.google.com include:sendgrid.net include:servers.mcsv.net ~all
Three includes. In practice this commonly resolves to nine to twelve lookups, because each provider nests its own. You are at or over the limit with three services and a record that looks minimal.
Why PermError is worse than a plain SPF failure
A PermError does not mean “SPF was inconclusive”. DMARC treats it as an SPF failure.
So if your domain publishes a DMARC policy of quarantine or reject, and DKIM is not independently passing and aligned, your mail is quarantined or rejected — because of a lookup count, not because anything about the sender is wrong.
This is the mechanism behind a specific and maddening experience: adding a new marketing tool to your SPF record breaks your order confirmations. The two have nothing to do with each other except a shared lookup budget.
Confirming it is your problem
Send a message to a Gmail address, open it, choose Show original, and read Authentication-Results. You are looking for:
spf=permerror
Any SPF checking tool that reports a lookup count will also tell you — you want the number of lookups, not the number of mechanisms.
Fixing it
Four approaches, best first.
1. Remove what you no longer use
Start here, because it is free and usually sufficient. SPF records accumulate. The trial ESP from two years ago, the migrated help desk, the CRM you replaced — each is still costing lookups.
Go through every include and confirm something still sends through it. Most over-limit records have at least one dead entry.
2. Stop sending from services that do not need to be in SPF
A service only needs to be in your SPF record if it sends mail using your domain in the envelope sender. A service that sends from its own domain and merely displays your name does not.
3. Replace includes with explicit IP ranges
Where a provider publishes stable IP ranges, listing them as ip4 costs zero lookups.
Be careful: this is a manual copy of data the provider controls. If they add a range and you do not update, mail from the new range fails SPF. Only do this for providers who document their ranges as stable, and record that you have done it, because the failure arrives months later with no obvious cause.
4. Use a subdomain for bulk mail
The structurally correct fix. Send marketing from mail.yourdomain.com and transactional from your main domain, each with its own SPF record and its own ten-lookup budget.
This has a benefit beyond the lookup count: it separates reputations. A marketing campaign that generates complaints stops affecting delivery of password resets and order confirmations, which is worth doing on its own merits.
Two neighbouring mistakes
Publishing two SPF records. Adding a second TXT record for a second provider is invalid. A domain may have exactly one SPF record; two means receivers see an error and both effectively fail. Merge the mechanisms into a single record.
Assuming SPF alone is enough. SPF breaks on forwarding — a mailing list or a forwarded message will usually fail SPF at the final destination. DKIM survives forwarding, and DMARC passes if either SPF or DKIM passes and is aligned. A correctly signed DKIM record makes an SPF PermError far less damaging, which is a good reason to make sure DKIM is right before you spend an afternoon flattening SPF.
Counting your own lookups
You can check this without a tool. Each include costs one lookup, plus whatever its own record costs. Follow one down:
dig +short TXT _spf.google.com
Google’s record contains further includes, each of which is another lookup, and those may contain more. Repeat until you reach records containing only ip4 and ip6 mechanisms, which cost nothing.
Rough real-world costs, useful for a quick estimate:
| Provider | Approximate lookups |
|---|---|
| Google Workspace | 3–4 |
| Microsoft 365 | 2–3 |
| A typical transactional provider | 1–3 |
| A marketing platform | 1–4 |
| Your web host’s default record | 1–2 |
These are approximations and providers change them without notice, which is precisely the problem — your lookup count can rise without you editing anything.
Why flattening is a trap as well as a fix
Replacing an include with explicit ip4 ranges is the most-recommended fix, and it deserves a warning that rarely accompanies it.
An include is a live reference: when your provider adds sending infrastructure, your record follows automatically. Flattening replaces that with a snapshot. When the provider adds a range and you do not update, mail from the new range fails SPF — and it fails for a subset of messages, intermittently, months after the change you made.
That is a genuinely nasty failure mode: partial, delayed, and disconnected from any action you remember taking.
So flatten only where a provider documents stable ranges, record that you have done it, and prefer the alternatives. Removing an unused include has no such downside, and a subdomain for bulk mail solves the problem structurally rather than by freezing a moving target.
Worked example
A store with a plausible, over-limit record:
v=spf1 include:_spf.google.com include:sendgrid.net
include:servers.mcsv.net include:spf.protection.outlook.com
include:_spf.oldhost.example ~all
Five includes, likely resolving to eleven or twelve lookups. PermError, so DMARC fails, so mail is quarantined wherever policy is enforced.
Working through it in order:
- The old host include is dead — the site migrated last year. Remove it. Down roughly two.
- Google and Microsoft are both present because mailboxes moved and nobody removed the old one. Remove whichever is no longer used. Down roughly three more.
- The marketing platform moves to a subdomain, taking its lookups off this record entirely.
The result is short, well under the limit, and — importantly — every remaining include is live rather than a flattened snapshot:
v=spf1 include:_spf.google.com include:sendgrid.net ~all
Nothing was flattened. Nothing needs maintaining. That is what a good fix looks like here.
Keeping it fixed
The awkward part of this problem is that you can fix it and re-break it without touching your record. A provider adding one include to their record can push you over the limit, and you will get no notification.
This is a good argument for checking on a schedule. NimblyMail evaluates SPF as one of six audit pillars weekly, reading the expected include from the mailer you actually have configured, so a record that has drifted out of tolerance gets reported rather than discovered by a customer.
Summary
- SPF allows ten DNS lookups;
include,a,mx,ptr,existsandredirectcount,ip4andip6do not. - Nested includes count too, so three providers often reach nine to twelve.
- Exceeding it gives PermError, and DMARC treats PermError as failure.
- Remove unused includes first, then consider explicit IPs or a dedicated subdomain.
- Never publish two SPF records.
- Make sure DKIM is passing, because it survives forwarding and satisfies DMARC on its own.
