SPF macros: the syntax, every macro letter, and worked examples from RFC 7208
What SPF macros such as %{i} and %{d} expand to, transformers and delimiters, where macros are allowed, RFC 7208 worked examples, and the lookup limit.
On this page
An SPF macro is a placeholder such as %{i} or %{d} that is replaced, while the SPF record is evaluated, with parameters of the message or connection. RFC 7208 (opens in a new tab) calls these character sequences macros, and section 7.2 (opens in a new tab) defines the letters: i is the client IP address, d the domain being evaluated, l the sender's local part. The expansion happens before the DNS query, as the exists mechanism (opens in a new tab) shows: its domain is expanded and the result is looked up as an A record, which makes decisions possible per user and per client IP address.
Where macros are allowed
Macros appear inside a domain-spec, the domain argument of a term. RFC 7208's grammar (opens in a new tab) defines domain-spec as a macro-string followed by a domain end, so every mechanism or modifier that takes a domain can carry macros: include (opens in a new tab), whose domain-spec is expanded under the section 7 rules before the recursive evaluation, a (opens in a new tab) and mx (opens in a new tab) with an optional domain, ptr (opens in a new tab), exists (opens in a new tab), and the modifiers redirect (opens in a new tab) and exp (opens in a new tab). The ip4 and ip6 mechanisms (opens in a new tab) take a network address rather than a domain-spec, so they carry no macros. The grammar also allows a macro-string in an unknown modifier, as section 4.6.1 (opens in a new tab) shows.
Check your domain now
See the SPF, DKIM and DMARC records your domain publishes and what to fix first. Free, no account needed.
The macro letters
RFC 7208 section 7.2 (opens in a new tab) lists the letters. Eight expand in term arguments; three are allowed only in the exp explanation text. The example column uses the values from the RFC's worked example in section 7.4 (opens in a new tab): sender strong-bad@email.example.com, client IP 192.0.2.3, PTR name mx.example.org.
| Macro | Expands to | Example |
|---|---|---|
%{s} |
the sender address | strong-bad@email.example.com |
%{l} |
the local part of the sender | strong-bad |
%{o} |
the domain of the sender | email.example.com |
%{d} |
the domain being evaluated | email.example.com |
%{i} |
the client IP address | 192.0.2.3 |
%{p} |
the validated domain name of the IP; the RFC marks it "do not use" | avoid |
%{v} |
in-addr for IPv4, ip6 for IPv6 |
in-addr |
%{h} |
the HELO/EHLO domain | the name the client announced |
%{c} |
the client IP in readable form, exp only |
192.0.2.3 |
%{r} |
the domain of the host doing the check, exp only |
the receiver's name |
%{t} |
the current timestamp, exp only |
seconds since 1970 |
Section 7.3 (opens in a new tab) adds the details behind the table. s, l and o keep their values through include and redirect chains, and when the original sender had no local part, postmaster is used. For IPv6 the i macro expands to a dot-separated form meant for %{ir}. The h macro takes the most recent HELO or EHLO parameter if the client sent more than one. The p macro expands to unknown when no validated name exists or the DNS lookup errors, and the RFC says it should not be published.
Transformers, reversal and delimiters
Between the letter and the closing brace, a macro can carry transformers and delimiters, defined in section 7.1 (opens in a new tab) as zero or more digits, an optional r, and then any of ., -, +, ,, /, _ or =. Section 7.3 (opens in a new tab) explains what they do. The value is split into parts on dots by default, or on the delimiter characters given; r reverses the parts; the digits keep that many right-hand parts, after any reversal, and must be nonzero; the parts are rejoined with . whatever they were split on. So with a client IP of 192.0.2.1, %{i} expands to 192.0.2.1 and %{ir} to 1.2.0.192. An uppercase letter, such as %{S} or %{I}, expands like the lowercase one and is then URL-escaped. Three escapes exist outside braces: %% is a literal percent sign, %_ a space, and %- a URL-encoded space, %20. A % followed by anything else is a syntax error: -exists:%(ir).sbl.example.org makes the whole evaluation return permerror, where -exists:%{ir}.sbl.example.org is legal.
Two length limits apply. An expanded domain name longer than 253 characters is truncated from the left, label by label, until it fits, and a single label is capped at 63 characters, which a long local part can exceed. The RFC also advises against s, l, o and h in mechanisms, because a record that depends on the sender address cannot be cached per domain and IP.
Worked examples
RFC 7208 section 7.4 (opens in a new tab) expands a set of macros for the sender strong-bad@email.example.com from the IPv4 client 192.0.2.3.
| Macro string | Expansion |
|---|---|
%{d} |
email.example.com |
%{d2} |
example.com |
%{d1} |
com |
%{dr} |
com.example.email |
%{d2r} |
example.email |
%{l-} |
strong.bad |
%{lr-} |
bad.strong |
%{l1r-} |
strong |
%{ir}.%{v}._spf.%{d2} |
3.2.0.192.in-addr._spf.example.com |
%{lr-}.lp._spf.%{d2} |
bad.strong.lp._spf.example.com |
%{d2}.trusted-domains.example.net |
example.com.trusted-domains.example.net |
Read the common pattern exists:%{ir}.%{v}._spf.%{d2} step by step for that connection. %{ir} reverses the client IP into 3.2.0.192. %{v} is in-addr because the client used IPv4. %{d2} keeps the last two labels of the evaluated domain, example.com. The receiver therefore queries 3.2.0.192.in-addr._spf.example.com for an A record, and the mechanism matches if any A record comes back. The RFC's own exists example (opens in a new tab) goes one step further, v=spf1 exists:%{ir}.%{l1r+-}._spf.%{d} -all, where the target expands to a name such as 1.2.0.192.someuser._spf.example.com, making decisions per user and client IP.
The p macro and why to avoid it
The p macro expands to the validated domain name of the client IP, which section 5.5 (opens in a new tab) obtains by a PTR lookup in in-addr.arpa or ip6.arpa followed by a forward lookup of each name returned. The RFC describes this procedure as slow, less reliable than other mechanisms when DNS errors occur, and a large burden on the .arpa name servers, concludes after years of deployment that it is unnecessary, and says the ptr mechanism should not be published. Section 7.3 (opens in a new tab) carries the same instruction for the p macro itself: it should not be published, and it falls back to unknown on any error.
Macros and the 10-lookup limit
A macro adds no lookup by itself, with one exception covered below; the term it sits in is what counts. RFC 7208 section 4.6.4 (opens in a new tab) names include, a, mx, ptr, exists and redirect as the terms that cause DNS queries, limits them to 10 per evaluation, and requires permerror past that; all, ip4, ip6 and exp are exempt. An exists term with macros is one of the ten, however many addresses its A records cover. The PTR records fetched for the ptr mechanism or the %{p} macro count inside the same limit, and each PTR record may trigger at most 10 address lookups. Receivers should also cap void lookups, queries that return no answer or a name error, at two. If a record is at the limit already, see SPF too many DNS lookups.
Check a record that uses macros
The free SPF, DKIM and DMARC check reads the public DNS records of the domain you enter, counts the DNS lookups the record costs and flags one close to or over the limit of 10, and reports a missing record, +all, ?all and ~all. It reads the record as published in DNS, and one check is a snapshot: records change. The SPF record generator builds a record from the services that send for your domain, with include, a, mx, ptr, exists and redirect each costing one lookup and ip4 and ip6 costing none. Whichever route you take, publish one SPF record per domain, replacing rather than adding, and see SPF record with a hard fail for the choice between ~all and -all.
Sources
- RFC 7208 section 7: macros (opens in a new tab)
- RFC 7208 section 7.1: formal specification (opens in a new tab)
- RFC 7208 section 7.2: macro definitions (opens in a new tab)
- RFC 7208 section 7.3: macro processing details (opens in a new tab)
- RFC 7208 section 7.4: expansion examples (opens in a new tab)
- RFC 7208 section 4.6.1: term definitions (opens in a new tab)
- RFC 7208 section 4.6.4: DNS lookup limits (opens in a new tab)
- RFC 7208 section 5.2: include (opens in a new tab)
- RFC 7208 section 5.3: a (opens in a new tab)
- RFC 7208 section 5.4: mx (opens in a new tab)
- RFC 7208 section 5.5: ptr (opens in a new tab)
- RFC 7208 section 5.6: ip4 and ip6 (opens in a new tab)
- RFC 7208 section 5.7: exists (opens in a new tab)
- RFC 7208 section 6.1: redirect (opens in a new tab)
- RFC 7208 section 6.2: exp (opens in a new tab)
Tools for this
-
SPF, DKIM and DMARC check
Check the email records a domain publishes today and see what to fix first.
-
SPF record generator
Build an SPF record for the services that send your email.
Frequently asked questions
Do SPF macros count toward the 10 DNS lookups?
The mechanism they sit in does. include, a, mx, ptr, exists and redirect each count once per evaluation whether or not their domain uses macros, so an exists term with a macro costs one lookup. ip4, ip6, all and exp do not count.
Can I write %{i} in the SPF record for my own mail server?
Yes, inside a domain-spec such as the argument of exists or include, where it is expanded before the DNS query. For a server with fixed addresses, ip4 and ip6 terms test the client IP against a network directly and cost no DNS lookups.
Why does my SPF checker show the macro unexpanded?
Macros are replaced by the receiving server while it evaluates the record for one specific message, using that message's parameters such as the sender address and client IP. The record published in DNS still contains the macro text, which is what a DNS lookup returns.
Check your domain now
See the SPF, DKIM and DMARC records your domain publishes and what to fix first. Free, no account needed.