Your SPF record looks fine and your mail provider says everything is configured, yet DMARC fails and messages land in spam. One quiet cause: the SPF record went over the 10-lookup limit.
The rule
RFC 7208 (section 4.6.4) caps SPF evaluation at 10 DNS queries, nested ones included. Go over and the receiver returns a permerror: the record is treated as invalid, SPF fails, and DMARC alignment fails with it.
Nothing warns the sender. And it can affect all the mail of the domain, not only the last provider you added.
What counts toward the limit
| Counts as a lookup | Free |
|---|---|
include:, a, mx, ptr, exists:, redirect=
|
ip4:, ip6:, all
|
Two details people miss:
-
mxis not one lookup. It costs one query for the MX records, plus one address lookup per mail server. - A second limit applies to void lookups (queries that return NXDOMAIN or no data): at most 2. Going over it is also a
permerror.
How to count
Resolve the tree by hand: follow every include: and redirect= into its own record, and count each mechanism that triggers a query. A record with three include: entries plus mx can already reach 7 once the nested includes are expanded.
Here is a typical record that crosses the line:
v=spf1 include:_spf.google.com include:sendgrid.net include:servers.mcsv.net include:spf.brevo.com mx ~all
It resolves to 11 lookups. Drop the Brevo include (a tool that is no longer used) and it falls to 9:
v=spf1 include:_spf.google.com include:sendgrid.net include:servers.mcsv.net mx ~all
Fixes, from simplest to most structural
- Remove unused includes. Old newsletter tools and past providers pile up. This alone often brings you under 10.
-
Replace includes with
ip4:/ip6:when a service has fixed, documented addresses that you control, for exampleip4:203.0.113.10instead ofinclude:mail.example.com. Do not do this for large providers such as Google or Microsoft: their ranges change. -
Flatten the record. Resolve every
include:,a,mxandredirect=into IP addresses, for a cost of zero lookups. It works with every receiver, but provider IPs change, so you must re-flatten regularly (monthly is a reasonable rhythm). - Split by subdomain. Send each provider from its own subdomain, with its own SPF record and its own 10-lookup budget, for example a marketing subdomain for the newsletter tool. This is the durable answer once you use more than five providers.
-
SPF macros such as
%{i}can build dynamic records that cut the number of static includes. It is the advanced option, and it deserves its own article.
Common mistakes
- Forgetting the lookups nested inside included records.
- Treating
mxas a single lookup. - Hardcoding IPs of big providers, then never noticing when they change.
- Ignoring void lookups, for example an include that points to a domain that no longer exists.
- Flattening once and never checking again.
A short action plan
- Count your current lookups.
- Remove what you no longer use.
- Pick a fix: flattening for a quick result, subdomains for a lasting structure.
- Test the new record before publishing it in DNS.
- Re-check every month.
If you want to skip the manual counting, the free SPF checker from CaptainDNS follows the includes, counts the lookups and detects errors, with no sign-up. The full guide, with more detail on each fix, is on the CaptainDNS blog.
Top comments (1)
Good clear breakdown, especially the point about mx costing more than one lookup. One extra check I would add is whether mx is needed at all. If the hosts listed in MX never send mail, dropping mx saves lookups without losing any cover. I would also test the new record with real receiver reports rather than only a checker, since DMARC aggregate reports show whether permerror is still happening in practice.