How receivers evaluate v=spf1
SPF is a TXT record at the domain in the envelope sender (the MAIL FROM address). The record is a list of terms read left to right, and each mechanism is a test against the connecting IP: ip4:203.0.113.0/24 matches a range, a and mx compare against the domain's own A or MX hosts, and include evaluates another domain's record. The first match wins, and its qualifier (+, -, ~ or ?) becomes the result.
A Google Workspace domain that also sends through one marketing service typically publishes v=spf1 include:_spf.google.com include:servers.mcsv.net ~all. The receiver resolves both includes, which carry their own ip4, ip6 and include terms, and only falls through to ~all when nothing matched. SPF says nothing about the From header a person sees. Tying the two together is DMARC's job.
Why the 10-lookup limit matters
RFC 7208 caps the number of DNS-querying terms a receiver will evaluate at 10, counted across the whole tree: include, a, mx, ptr, exists and the redirect modifier. ip4 and ip6 are free, which is why replacing an include with its address ranges lowers the count. A receiver that reaches term eleven stops and returns PermError, which DMARC treats as an SPF failure.
The checker increments one shared counter as it walks the tree, so a provider include that itself holds three includes costs four lookups, exactly as a receiver would count it. The Include tree card puts a 1 lookup pill next to each term that costs one and shows how many terms each child contains. The walk stops after 40 nested records and says so.
Reading the Analysis card finding by finding
A failure means receivers would return PermError or the record protects nothing: a second v=spf1 record, a term that is not valid syntax, an ip4 or ip6 value that is not an address or CIDR range, an include whose target has no SPF record, more than 10 lookups, more than two void lookups, or +all. Clear every failure before you look at anything else.
Warnings cover things that still work but should change: 8 or 9 lookups, a ptr mechanism (deprecated in RFC 7208 section 5.5), ?all, a missing all term, or a record longer than 450 characters that risks a truncated DNS response. Inside the tree, terms after all are marked as never evaluated and unknown modifiers as ignored. An include that uses macros cannot be expanded statically and gets a note instead, as does an include loop.
Fixing the common failures
Too many lookups: drop services you no longer send through, move bulk mail to a subdomain with its own record, or replace an include that wraps a few fixed ranges with those ip4 and ip6 terms, remembering that a flattened copy goes stale when the provider changes addresses. Rebuild the line with the SPF Generator.
Two records: merge them into one line. A second v=spf1 line is the usual result of following a vendor's setup guide too literally. A missing include target usually means the vendor renamed or retired the domain, so take the current include from their documentation. An invalid term is most often a stray character, a space inside a CIDR range or a typo in a mechanism name.
- Keep ~all until DMARC reports confirm every sender passes, then move to -all if you want receivers to reject unlisted hosts outright.
- Add each new sending service to the existing record instead of creating another TXT line.
- Re-run the checker after every DNS change. Results are cached for about a minute.
What the checker cannot tell you
The evaluation is static: it reads the record as published and counts lookups the way a receiver would, but it does not test a particular message or sending IP, so it cannot say whether yesterday's bounce was an SPF failure. For that, read the Authentication-Results header in the Email Header Analyzer or the per-source rows in the DMARC Report Analyzer.
Queries go to the public resolvers at Cloudflare, Google and Quad9 with a three-second timeout, so a transient DNS problem can produce a failed include that clears on retry. Lookups are not stored. The domain you enter is mirrored into the page URL as ?domain= so the result can be shared.