%{ir}%i198.51.100.4242.100.51.198Expand SPF macros — %{i}, %{s}, %{d}, %{l}, %{o}, %{h}, %{v}, %{p} — with your own evaluation context. See exactly what each macro expands to before publishing the record, and understand the per-IP and per-sender authorisation patterns most receivers never document.
exists:42.100.51.198.in-addr._spf.example.com%{ir}%i198.51.100.4242.100.51.198%{v}%vin-addrin-addr%{d}%dexample.comexample.com
RFC 7208 §7 defines a tiny templating language embedded inside
SPF mechanism values. At evaluation time, receivers substitute placeholders
like
%{i} (the client IP) and
%{s} (the sender address) with values from the current
message. The result lets you put per-sender or per-IP logic into a single static
SPF record — a trick most ESPs use and almost no admin debugs comfortably because
there's never a clean way to see the expansion.
The debugger above lets you paste any mechanism with macros, set the evaluation context (client IP, sender, HELO, domain), and see the final expanded form plus a macro-by-macro breakdown showing the base value, the transform, and the output.
| Macro | Meaning | Common use |
|---|---|---|
%{s} | Sender — full envelope MAIL FROM address | Per-sender allowlisting |
%{l} | Local-part of the sender |
Mailbox-specific routing (e.g. only
alerts@ via Pagerduty)
|
%{o} | Domain of the sender | Per-tenant SPF includes in shared infra |
%{d} | Current evaluation domain | Build self-referential records |
%{i} | Client IP (IPv6 → 32 dot-separated nibbles) | Per-IP allowlisting via DNS zone |
%{p} | Validated PTR (discouraged) | Almost always returns "unknown" — avoid |
%{v} |
Literal in-addr for IPv4 or
ip6 for IPv6
| Build reverse-DNS-style zones |
%{h} | HELO/EHLO domain from the SMTP session | Detect mismatch between HELO and MAIL FROM domain |
%{c} | Client IP (textual form). Only valid in exp=. | Explanation strings |
%{r} | Receiving server domain. Only valid in exp=. | Explanation strings |
%{t} | Current Unix timestamp. Only valid in exp=. | Time-bound explanation strings |
Every macro accepts optional transformations between the letter and the
closing brace:
%{letter[digits][r][delimiter]}.
%{i2} on 192.0.2.10 yields
2.10 (last 2 dot-separated parts).
%{ir} on
192.0.2.10 yields 10.2.0.192.
. - + , / _ =. Splits the base value on any of the listed
characters before applying digits/reverse. Example: %{l-} on
foo-bar-baz splits on - giving three parts.
The SPF Flattener can only resolve include chains to static IPs at the time of evaluation. Macros are evaluated per-message
using runtime context (the client IP, the sender). A record containing exists:%{ir}.%{v}._spf.%{d} cannot be pre-flattened — there is no single static answer. The Flattener
flags this with a macro_in_mechanism
warning and skips the expansion.
%{p} is the validated PTR domain — the receiver runs
a reverse-DNS lookup on the client IP and confirms the forward A record matches.
Per RFC 7208 §7.2, if validation fails (the typical case for IPs without proper
FCrDNS),
%{p} expands to unknown. Many
receivers skip the PTR lookup entirely for performance. Combined: %{p} almost always returns
unknown — design around it, not for it.
The presets above the tool cover the most-cited patterns. Two live examples worth knowing:
exists:%{ir}.%{v}._spf.%{d} looks
up
10.2.0.192.in-addr._spf.example.com for an IPv4 client. Publish
A records under that zone to authorise individual IPs without bloating the
static SPF record.
exists:%{l}.%{o}._spf.%{d} looks
up
alice.user.example.com._spf.example.com. Lets you restrict
particular mailboxes to particular send paths — useful when alerts@
should only ever originate from your monitoring infra.
Read the complete SPF guide to learn more.
Continuous monitoring, aggregate report parsing, and actionable insights for all your email authentication protocols.
Start Free