Authentication7 min read

SPF too many lookups: how to fix the 10-lookup limit

Every "include:" and "a" and "mx" counts toward SPF's hard 10-DNS-lookup limit. Cross it and your record becomes permerror — which is treated exactly the same as no SPF at all.

SPF has a silent failure mode that catches out most growing companies exactly when they're expanding their sending stack. The record looks valid. Online validators say it parses. Mail goes out. And yet Gmail logs spf=permerror in the Authentication-Results header on every message, which it treats identically to having no SPF at all. The cause is RFC 7208's hard limit of ten DNS lookups per SPF evaluation, and the stack of modern ESPs that each chew through multiple lookups of their own.

TL;DR — the four fixes

1) Audit your current lookup count with dmarcian or spf-record.com. 2) Remove unused includes (most records have at least one). 3) Either flatten remaining includes to IP lists, or — better — move each vendor onto its own sending subdomain so lookups don't stack. 4) Monitor — flattening drifts as vendor IPs change.

Why the limit exists

When a receiver evaluates SPF, it walks the whole include chain, doing a DNS lookup for each include:, a, mx, exists: and redirect= directive. Without a cap, a malicious actor could construct an SPF tree that forces receivers to make hundreds of lookups per message — a DNS amplification attack. The 10-lookup cap was baked into RFC 4408 (now 7208) as a DoS mitigation. It hasn't moved since. Receivers enforce it strictly; crossing it makes the entire record evaluate to permerror.

What counts toward the limit

These directives consume a lookup each:

  • include: — one lookup, plus every lookup the included record makes, recursively.
  • a and a:domain — one lookup each.
  • mx and mx:domain — one lookup for the MX query itself, plus one per MX host returned (some receivers).
  • exists: — one lookup.
  • redirect= — one lookup, plus its tree.

These don't count:

  • ip4: and ip6: — literal IPs, no lookup needed.
  • all, ~all, -all, ?all.

ptr was in the original RFC but is now deprecated — don't use it.

Typical offenders — lookups per include

Common senders, and the lookups they burn when you include them (as of early 2026 — these drift):

  • Google Workspace (_spf.google.com) — 1 lookup.
  • Microsoft 365 (spf.protection.outlook.com) — 1 lookup.
  • SendGrid (sendgrid.net) — 3 lookups.
  • Mailgun (mailgun.org) — 3 lookups.
  • Amazon SES (amazonses.com) — 1 lookup.
  • Mailchimp (servers.mcsv.net) — 1 lookup.
  • HubSpot — 1 lookup per region include.
  • Marketo (mktomail.com) — 2 lookups.
  • Salesforce (_spf.salesforce.com) — 4 lookups.
  • Intercom — 1 lookup.

Add any three or four of these together and you're at the cliff. Salesforce + SendGrid + M365 = 8 lookups with nothing else. Add Marketo and HubSpot and you're over.

How to audit your current lookup count

Three options, in order of convenience:

  • dmarcian SPF Surveyor. Paste your domain, it returns a tree diagram and a total lookup count. Free for a handful of queries per day.
  • spf-record.com. Similar — tree view plus per-include breakdown.
  • Manual dig. Query your SPF, then recursively query every include. Tedious but the only way to be sure which specific include is bloating things on a given day.
dig TXT yourdomain.com +short
dig TXT sendgrid.net +short  # follow includes manually

Fix 1 — SPF flattening

Flattening replaces include: references with literal ip4: / ip6: lists. Because IP literals don't count toward the 10-lookup limit, the resulting record evaluates with zero includes.

Pros: immediate fix, works everywhere, simple mental model.
Cons: vendor IPs change. When SendGrid adds a new sending pool or Microsoft rotates their 40.x.x.x ranges, your flattened record silently goes out of date and legitimate mail starts failing SPF. You're on the hook for re-flattening monthly, or paying a service (EasyDMARC, Valimail, PowerDMARC) to monitor and update the record for you.

Fix 2 — Dedicated subdomain per vendor

The cleanest architectural fix. Instead of one omnibus SPF record on yourdomain.com that includes everything, put each sender on its own subdomain:

  • send.yourdomain.com — Marketo only. SPF v=spf1 include:mktomail.com -all. 2 lookups.
  • mail.yourdomain.com — SendGrid only. SPF v=spf1 include:sendgrid.net -all. 3 lookups.
  • yourdomain.com — M365 (corporate). SPF v=spf1 include:spf.protection.outlook.com -all. 1 lookup.

Pros: each subdomain has plenty of headroom under 10, per-vendor DMARC reports are clean and attributable, no flattening maintenance, vendor IPs can change without breaking you.
Cons: more DNS records to manage, and you have to set the From address correctly in each vendor so SPF aligns against the right subdomain.

Fix 3 — SPF macros

SPF supports macros (%{i}, %{d}) that let you construct dynamic lookup names. In theory you can write a single exists: expression that resolves differently per-sender-IP. In practice this is fragile, opaque, and almost never maintained correctly. Skip unless you genuinely need it.

Fix 4 — Remove unused includes

Almost every SPF record we audit contains at least one include from a service the company stopped using months ago. Survey your actual sending stack this quarter. If you removed Mailgun in favour of Postmark but the SPF still includes mailgun.org, that's three free lookups you can reclaim. This is often the lowest-effort highest-impact fix.

How to verify the fix

After any change, re-run the SPF validator and confirm:

  • Total lookup count is ≤ 10 (target ≤ 8 for headroom).
  • Record still ends with -all or ~all.
  • Send a test message and inspect the receiving side's Authentication-Results header:
Authentication-Results: mx.google.com;
       spf=pass (google.com: domain of user@yourdomain.com
       designates 40.92.0.10 as permitted sender)

spf=pass is the target. spf=permerror means you're still over 10; spf=softfail means the sending IP isn't in the record at all.

Flattening is a tradeoff — subdomain strategy is cleaner

Flattening solves the lookup problem but introduces a new maintenance burden. Every time your ESP rotates IP ranges, your flattened record drifts. The subdomain strategy avoids this entirely by letting each vendor resolve its own includes at send time. If you're architecting for the long term, start with subdomains.

Frequently asked questions

Does the 10-lookup limit apply per message or per domain?

Per SPF evaluation, which is per message. The receiver restarts the lookup count from zero for each incoming message, but your record either fits in 10 or it doesn't — there's no way to optimise per recipient.

Will flattening break DKIM or DMARC?

No. DKIM is independent of SPF. DMARC uses SPF pass/fail plus alignment; flattening doesn't change alignment, and if the IPs are correct SPF still passes.

How often do ESP IP ranges change?

Google and Microsoft update ranges monthly. SendGrid and Mailgun rotate pools quarterly. Amazon SES can change weekly for new regions. If you flatten, commit to re-flattening monthly at minimum.

What about the 'void lookup' limit — is that a real thing?

Yes. RFC 7208 also caps void lookups (those returning NXDOMAIN or empty) at 2. If one of your includes points at a dead domain, you can hit this limit with only a handful of active includes. Another reason to audit — dead includes bite twice.
Related reading

Check your deliverability across 20+ providers

Gmail, Outlook, Yahoo, Mail.ru, Yandex, GMX, ProtonMail and more. Real inbox screenshots, SPF/DKIM/DMARC, spam engine verdicts. Free, no signup.

Run Free Test →

Unlimited tests · 20+ seed mailboxes · Live results · No account required