CertaDNS
Skip to lesson

SPF Fundamentals · lesson 3 of 6

Mechanisms and evaluation order

After this lesson you can

Predict the SPF result for a given sending IP against any record.

Assumes you have read The shape of an SPF record.

There are seven mechanisms. Six of them can match a connecting IP address; the seventh matches everything. Evaluation is left to right, and the first match ends it — nothing after a match is ever considered.

The seven

MechanismMatches whenDNS lookups
ip4:198.51.100.7The connecting IP is that address, or in that CIDR range if one is given.0
ip6:2001:db8::/32Same, for IPv6.0
aThe connecting IP is one of the A/AAAA records of the current domain. With an argument (a:mail.example.com), of that name instead.1
mxThe connecting IP is an address of one of the domain’s MX hosts.1, plus one per MX host
include:other.exampleEvaluating that domain’s SPF record produces a pass.1, plus whatever that record costs
exists:%{i}.check.exampleThe constructed name resolves to anything at all.1
ptrReverse DNS for the connecting IP resolves back to the domain.Several

Do not use ptr

RFC 7208 §5.5 says it "SHOULD NOT be used" — it is slow, it loads the reverse-DNS infrastructure of networks you do not own, and some receivers skip it entirely, which means a record relying on it behaves differently depending on who is evaluating it. If you see ptr in a record you inherited, replacing it is a straightforward improvement.

Order decides the answer

Because the first match wins, two records with identical terms in different orders can produce different results. Consider:

v=spf1 ip4:198.51.100.0/24 -all       -> 198.51.100.7 passes
v=spf1 -all ip4:198.51.100.0/24       -> 198.51.100.7 FAILS

In the second, -all matches everything, so it matches first and evaluation stops. The ip4 term after it is unreachable. This is not a hypothetical: it happens when someone appends a new sender to the end of a record without noticing where all is.

Tracing a record by hand

Take Cloudflare’s record and a hypothetical connection from 173.245.48.10:

$ dig +short TXT cloudflare.com | grep spf1
"v=spf1 ip4:199.15.212.0/22 ip4:173.245.48.0/20 include:_spf.google.com ... -all"
Checked 2026-09-12, abbreviated.
  • ip4:199.15.212.0/22 — is 173.245.48.10 in 199.15.212.0–199.15.215.255? No. Move on.
  • ip4:173.245.48.0/20 — is it in 173.245.48.0–173.245.63.255? Yes. Match. The qualifier is the implicit +, so the result is pass, and the six includes after it are never evaluated.

That last point has a practical consequence: putting your highest-volume sender early in the record saves receivers the DNS work of evaluating everything before it. It does not change any result, only the cost of reaching it.

exists, and macros

exists is the one mechanism that does something genuinely different: it constructs a DNS name from macros — %{i} for the connecting IP, %{d} for the domain, %{s} for the sender — and matches if that name resolves to anything.

v=spf1 exists:%{i}._spf.example.com -all

With a DNS server that can answer dynamically, this turns SPF from a static list into a query: the record asks "is this specific IP authorised right now?" and the answer can change without editing anything. It is how dynamic SPF implementations work, and it is the one approach that solves the lookup limit without the drawbacks of flattening. It also requires a DNS provider that supports it, which most do not.

Try it on a real domain

Free, no account, public DNS only.

Last reviewed