%{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