toolhq.io

All posts
6 min readby Jameel Haider

The SPF 10 DNS lookup limit, nested includes, and why flattening is risky

SPF has a hard ceiling that quietly breaks working email: a receiving server must give up after evaluating 10 DNS lookups in a single SPF check, and when your record needs more than that the result is PermError, which many receivers treat as a failure.

What counts toward the 10

The limit is set in RFC 7208. It counts mechanisms that trigger DNS resolution, not the total characters in the record. The mechanisms that cost a lookup are include, a, mx, ptr, and exists, plus the redirect modifier. The ip4, ip6, and all mechanisms cost nothing because they resolve nothing.

MechanismCounts toward limitNote
include:YesAnd everything it pulls in counts too
aYesEach a is one lookup
mxYesPlus one per MX host returned, up to a second cap of 10
ptrYesDiscouraged entirely, slow and unreliable
exists:Yes
redirect=Yes
ip4: / ip6:NoInline addresses, free
allNoAlways free

Why nested includes overflow

The trap is that include is recursive. When you write include:_spf.google.com, the receiver fetches that record, and whatever includes it contains are counted against your budget too. A single vendor include often expands into three or four nested lookups.

v=spf1 include:_spf.google.com include:servers.mcsv.net include:sendgrid.net ~all

That looks like three lookups. In practice Google alone expands to several nested includes, and once you add a couple of marketing vendors you cross 10 without adding anything visible. The record still looks short. The failure only appears when a receiver counts the full expanded tree.

PermError is a real failure

A PermError is not a soft warning. Under a strict DMARC policy, SPF returning PermError means the SPF leg of DMARC does not pass, and if DKIM is not aligned the message fails DMARC and can be rejected. The worst part is that it is intermittent in appearance: the record that passed last month fails this month because a vendor added one more nested include on their side, with no change to your own record.

What flattening actually does

SPF flattening resolves every include, a, and mx ahead of time and bakes the final list of IP addresses directly into your record as ip4 and ip6 entries. Since inline addresses cost zero lookups, a flattened record can hold hundreds of ranges and still pass.

v=spf1 ip4:35.190.247.0/24 ip4:64.233.160.0/19 ip4:66.102.0.0/20 ... -all

Why flattening is risky

Flattening moves the problem rather than removing it. When a vendor changes their sending IP ranges, their own SPF record updates automatically and every normal include picks up the change. Your flattened record does not. It holds a frozen snapshot, so the day the vendor rotates IPs, legitimate mail starts failing SPF and you have no warning. A flattened record is only safe if something re-resolves and republishes it on a schedule, and that automation is now a piece of infrastructure you have to own and monitor.

A calmer fix is usually to prune. Remove vendors you no longer send through, drop ptr entirely, and consolidate where one provider covers several needs. Most records that overflow do so because of dead includes nobody removed.

When you are reworking a record, build it deliberately and check the lookup count before you publish with the SPF Generator.