SPF record explained: syntax, qualifiers and the 10-lookup trap
SPF record explained for practitioners: v=spf1 syntax, include vs ip4, ~all vs -all, the 10-DNS-lookup limit, and the mistakes that cause permerror.
If you want an SPF record explained without the marketing gloss, here it is: SPF is a TXT record on your domain that lists which servers are allowed to send email claiming to come from that domain. Receiving mail servers look up the record, check the connecting IP against the list, and produce a result — pass, fail, softfail, or one of several error states. That result feeds into spam filtering and, increasingly, into DMARC. The record itself is one line of text, but the syntax has enough sharp edges that a large fraction of production SPF records are subtly broken.

The anatomy of an SPF record
An SPF record is a single TXT record at the apex of the domain (or on a subdomain, if that subdomain sends mail). A typical one looks like this:
example.com. TXT "v=spf1 ip4:203.0.113.0/24 include:_spf.google.com mx ~all"
Reading left to right:
| Element | Meaning |
|---|---|
v=spf1 | Version tag. Must be first, exactly this string. |
ip4:203.0.113.0/24 | Authorise this IPv4 address or CIDR range. ip6: is the equivalent for IPv6. |
include:_spf.google.com | Fetch the SPF record of another domain and evaluate it. If that record passes, this mechanism matches. |
mx | Authorise the IPs of the domain's MX hosts. |
a | Authorise the IPs the domain's A/AAAA records resolve to. |
~all | Catch-all: everything not matched above gets a softfail. |
Mechanisms are evaluated left to right, first match wins. Once a mechanism
matches, evaluation stops and the qualifier on that mechanism becomes the result.
Nothing after the match is ever looked at — which is why all only makes sense as the
final mechanism, and anything written after it is dead text.
Qualifiers: ~all vs -all (and why +all is a liability)
Each mechanism carries one of four qualifiers, with + (pass) being the implicit
default:
| Qualifier | Result on match | Typical receiver behaviour |
|---|---|---|
+ | pass | Accept as authorised |
- | fail | Strong signal to reject or junk |
~ | softfail | Accept but treat with suspicion; counts as fail for DMARC |
? | neutral | No opinion either way |
The qualifier you'll agonise over is the one on all. ~all says "anything else is
probably not us"; -all says "anything else is definitely not us". In a pre-DMARC
world the difference mattered a great deal. Under DMARC, both softfail and fail count
as an SPF failure for alignment purposes, so the practical gap has narrowed — many
teams run ~all indefinitely and let DMARC do the enforcement.
What you must never publish is +all. It authorises the entire internet to send as
your domain, which makes the record worse than useless: spammers get an SPF pass on
forged mail. It still turns up in the wild, usually as a "temporary" fix for a delivery
problem that someone forgot to revert.
The 10-DNS-lookup limit
RFC 7208 caps SPF evaluation at 10 DNS-querying mechanisms per check. The
mechanisms that count are include, a, mx, ptr, exists and the redirect
modifier. ip4 and ip6 are free, because they need no lookup. Crucially, the count
is recursive: an include that itself contains three includes costs four lookups,
not one.
Exceed the limit and the result is permerror — a permanent error that most receivers
treat as a failure, and that DMARC treats as no SPF result at all. This is the most
common way a working email setup silently degrades: you add a CRM, a helpdesk tool and
a marketing platform over eighteen months, each adds an include, and one day the
nested lookups tip past ten.
This is why SPF flattening exists: replacing include mechanisms with the literal
ip4/ip6 ranges they resolve to, bringing the lookup count back down. It works, but
it trades one problem for another — providers change their sending ranges without
notice, so a flattened record goes stale unless something re-resolves and republishes
it regularly. If you flatten by hand, you've signed up for a recurring maintenance
task. There is also a second, lesser-known limit: no more than two "void lookups"
(queries returning NXDOMAIN or an empty answer) per evaluation, which catches records
referencing decommissioned services.
Common mistakes worth checking for today
Multiple SPF records. A domain must have exactly one TXT record starting with
v=spf1. Two or more is an automatic permerror — receivers don't pick one or merge
them, they fail the check outright. This usually happens when a second team or a SaaS
onboarding guide says "add this TXT record" and nobody checks whether one already
exists. The fix is to merge the mechanisms into a single record.
all too early, or trailing junk after it. Everything after the first matching
mechanism is ignored, so v=spf1 ~all include:_spf.google.com softfails all your
legitimate mail.
ptr mechanisms. Deprecated by RFC 7208 — slow, unreliable, and several large
receivers skip or penalise them. Replace with ip4/ip6 ranges.
SPF on the apex but not on sending subdomains. SPF applies to the exact domain in
the envelope-from. If notify.example.com sends mail, it needs its own record;
the apex record does not cascade down.
Forgetting that forwarding breaks SPF. A message forwarded by a mailing list or a personal forwarding rule arrives from the forwarder's IP, which your record doesn't authorise. This is by design and is precisely why DMARC accepts either SPF or DKIM alignment — DKIM survives forwarding, SPF doesn't.
Keeping it from rotting
An SPF record is correct on the day you publish it and decays from there: providers
re-IP, includes get added, old services linger in the record years after the contract
ended. The useful discipline is to treat the record as monitored infrastructure rather
than set-and-forget DNS — re-validate the lookup count, check for duplicate records,
and confirm every include still resolves.
DomainOps runs SPF, DKIM and DMARC health checks continuously across every domain you monitor and alerts you when a record breaks or drifts. If you just want a one-off check, run your domain through our free email auth audit.