📨 SPF Syntax Reference

Every SPF record is a TXT record beginning with v=spf1 followed by a sequence of mechanisms, modified by qualifiers, and optionally ended with a redirect or exp modifier. This reference covers every component of the SPF syntax defined in RFC 7208.

How an SPF Record Works

When a receiving server gets an email, it checks the sending IP against the sender domain's SPF record. Each mechanism is evaluated left to right. The first mechanism that matches determines the result. The qualifier on that mechanism determines whether it's a Pass, Fail, SoftFail, or Neutral outcome.

v=spf1 include:_spf.google.com ip4:203.0.113.5 -all
v=spf1: version header
include:…: mechanism
ip4:…: mechanism
-all: qualifier + mechanism

a vs mx vs ip4 vs ip6: Which One Authorises Your Sender

Four mechanisms authorise an IP address, and they differ in what they cost you against the 10-term limit. Two of them resolve a name at delivery time; ip4 and ip6 carry the address in the record itself.

Mechanism What it authorises Lookup cost Reach for it when
a Whatever the A and AAAA records of the domain resolve to. Name a host after the colon (a:mail.example.com) or leave it off to use the sending domain. One term. No cap on how many addresses come back. The sending host is already named in DNS and its address can change without anyone editing the SPF record.
mx The addresses of the hosts in the domain's MX records. One term, but no more than 10 address queries inside it. Past that the record is a permerror. The same machines that receive your mail also send it. Common on a self-hosted server, almost never true on hosted mail.
ip4: A literal IPv4 address or CIDR block: ip4:192.0.2.0/24. None. It costs nothing against the 10-term limit. You know the address and it is stable. It costs no lookups at all, so it is what an expensive mechanism gets converted into when a record runs out of them.
ip6: A literal IPv6 address or CIDR block: ip6:2001:db8::/32. None. Your sender has an IPv6 address. A connection arriving over IPv6 is compared against IPv6 terms only, and no ip4 term can ever match one.

⚙️ Mechanisms

Mechanisms define which senders are authorised. They are evaluated left to right; the first match wins.

include DNS lookup
include:<domain>
View reference →
a DNS lookup
a[:<domain>][/prefix-length][//ipv6-prefix]
View reference →
mx DNS lookup
mx[:<domain>][/prefix-length][//ipv6-prefix]
View reference →
ip4
ip4:<ip-address>[/prefix-length]
View reference →
ip6
ip6:<ip6-address>[/prefix-length]
View reference →
ptr DNS lookup
ptr[:<domain>]
View reference →
exists DNS lookup
exists:<domain>
View reference →
all
[qualifier]all
View reference →

🔀 Modifiers

Modifiers change how SPF evaluation works. They are processed after all mechanisms.

redirect DNS lookup
redirect=<domain>
View reference →
exp DNS lookup
exp=<domain>
View reference →

🎛️ Qualifiers

Qualifiers prefix mechanisms to control the outcome when a mechanism matches.

+ (Pass)
+all
View reference →
- (Fail)
-all
View reference →
~ (SoftFail)
~all
View reference →
? (Neutral)
?all
View reference →

SPF Macros: %{i}, %{d} and the Rest

A macro is a character sequence the receiver replaces at evaluation time with a parameter of the message or the connection (RFC 7208 §7). They turn a static record into a query the receiver builds per message, which is the only way exists does anything useful. Almost nobody needs them, and the few who do run their own reputation service.

The eleven macro letters

Eight expand inside a term argument. The last three are legal only inside exp= text, where they build the human-readable rejection message.

Macro Expands to Valid in
%{s} <sender>, the full MAIL FROM address term arguments
%{l} local-part of <sender>, the bit before the @ term arguments
%{o} domain of <sender> term arguments
%{d} <domain>, the domain currently being evaluated term arguments
%{i} <ip>, the connecting client address term arguments
%{p} the validated domain name of <ip>. The RFC says "do not use" term arguments
%{v} the string "in-addr" for IPv4, or "ip6" for IPv6 term arguments
%{h} the HELO/EHLO domain term arguments
%{c} SMTP client IP in readable format exp= only
%{r} domain name of the host performing the check exp= only
%{t} current timestamp exp= only

Transformers and delimiters

A letter can carry digits, an r, or both. r reverses the value, splitting on dots by default: with a client IP of 192.0.2.1, %{i} gives 192.0.2.1 and %{ir} gives 1.2.0.192. Digits keep that many right-hand parts after any reversal, and the count must be non-zero. Ask for more parts than exist and you get all of them. You can split on something other than a dot by naming a delimiter from . - + , / _ =, but the parts are always rejoined with dots.

Three sequences stand outside the braces: %% is a literal percent sign, %_ is a space, and %- is a URL-encoded space (%20).

A reverse-DNS-style allowlist query

v=spf1 exists:%{ir}.sbl.example.org -all

A message arriving from 192.0.2.1 makes the receiver look up 1.2.0.192.sbl.example.org. If that name resolves to any A record the exists mechanism matches, so the record authorises whichever addresses the operator of sbl.example.org has published, and the zone can change without anyone editing the SPF record.

The mistake that costs you a permerror

A % followed by anything other than {, %, - or _ is a syntax error, and a syntax error anywhere in the record is a permerror for the whole thing. Round brackets are the one people reach for:

exists:%(ir).sbl.example.org   ← permerror
exists:%{ir}.sbl.example.org   ← correct

Our take: a permerror is not a soft failure. Receivers treat it as an unauthenticated message, so one bracket takes down authentication for every recipient at once. Paste the record into the SPF checker after any macro edit.

⚠️ The 10 DNS Lookup Limit

SPF allows a maximum of 10 DNS lookups per evaluation (RFC 7208 §4.6.4). Mechanisms that require DNS lookups are marked above. Exceeding 10 causes a permerror, which most receivers treat as a hard SPF failure. Use ip4: and ip6: where possible to stay under the limit.

🔧 Validate or Build Your SPF Record

Use these tools to check your current SPF record or build a new one from scratch.

SPF Checker → SPF Builder →