SPF macros are one of those corners of email authentication that can make an otherwise simple record suddenly look like a miniature programming language.
Most admins do not need them. Most admins who inherit them eventually wish they did not have them.
That does not mean macros are imaginary or unsupported. They are part of RFC 7208 Section 7. It means their practical value is narrow, while their debugging and operational cost is very real.
An SPF macro is a placeholder inside part of an SPF record that gets expanded while the receiver is evaluating SPF.
Instead of publishing only fixed strings such as:
example.com. IN TXT "v=spf1 include:_spf.example.net -all"you can publish terms whose target changes based on the message being checked.
For example:
example.com. IN TXT "v=spf1 exists:%{i}._spf.example.net -all"In that style, %{i} is a macro for the connecting client IP address. The receiver expands it during SPF evaluation and then queries the resulting DNS name.
RFC 7208 defines macro letters such as %{i} for the sender IP, %{d} for the current domain, %{s} for the sender, %{l} for the local-part, and %{h} for the HELO name. There are also transformers and delimiters that let a publisher reverse labels, truncate parts, or split values before building the final DNS name.
That flexibility is exactly why macros deserve caution. Once they are in play, an SPF record stops being just a static authorization list and starts depending on runtime string expansion.
Macros are allowed in SPF macro-strings and in domain-spec fields that are expanded during evaluation. In practice, admins usually encounter them in four places.
exists mechanismThis is the most common place to see macros used for live authorization logic.
RFC 7208 defines exists as a designated-sender mechanism, and the queried domain name can be built with macros. A pattern like this says, in effect, "construct a DNS name from message-specific data, then see whether any A record exists there":
example.com. IN TXT "v=spf1 exists:%{i}._spf.example.net -all"That design can support highly dynamic authorization schemes, but it also means SPF correctness now depends on generated DNS names, not just a small fixed policy.
redirect= targetsredirect= is often used without macros and is usually much easier to reason about that way.
But because domain-spec is macro-expandable, a publisher can do things like:
example.com. IN TXT "v=spf1 redirect=%{d}._spf.example.net"That is legal SPF, but it usually makes policy inheritance harder to audit than a plain static redirect target like redirect=_spf.example.net.
If you are comparing these two patterns, SPF redirect vs include, and common SPF mistakes is the companion post to read next.
exp= explanation stringsRFC 7208's exp= modifier can point to a DNS name that returns text explaining an SPF fail, and that target can also use macros.
Historically, people used this to generate fairly specific rejection text. Operationally, though, it is far less important than it once seemed. Modern deliverability work rarely improves because an SPF record generated a clever custom explanation string.
%{p} or other old-looking constructsSome inherited policies contain %{p}, which is tied to validated domain-name lookup behavior. RFC 7208 explicitly notes that evaluating the ptr mechanism or the %{p} macro involves PTR-related work and separate limits. That is already a warning sign.
If a modern SPF record still depends on PTR-style logic, treat it as suspicious until proven necessary.
The standard is also very clear that the ptr mechanism is "do not use". That alone should make most admins pause before embracing macro-heavy designs that drift back toward PTR-driven behavior.
Macros were not added just to make SPF look clever.
They exist because some publishers wanted policy decisions to vary by runtime inputs, such as:
That can support designs such as:
In other words, macros are real tools for special cases.
The problem is that most domains do not have those special cases. They just need to authorize known outbound services cleanly and predictably.
This is the practical heart of the topic.
A static SPF record is often understandable by inspection:
v=spf1 ip4:192.0.2.10 include:_spf.example.net -allA macro-based record usually is not. You have to know the runtime sender, runtime IP, expansion rules, and downstream DNS structure before you can tell what policy will actually be checked.
That raises the cost of every later task:
If the goal is a policy another admin can understand six months from now, macros are usually a step in the wrong direction.
RFC 7208 already puts strict limits on DNS-query-causing SPF terms. exists and redirect count toward those limits, and macro-based patterns often encourage more elaborate DNS dependency chains than static policies do.
That means more chances for:
permerror from lookup-limit excessIf SPF is already close to the 10-lookup edge, dynamic macro logic is rarely the thing that makes the policy safer.
If lookup pressure is already a problem, SPF 10-DNS-lookup limit: why it happens, how to audit includes, and mitigation patterns is the more relevant fix path.
RFC 7208's security and privacy sections are not subtle here.
If a macro string uses sender-specific elements such as %{l} or %{s}, the receiver may end up querying DNS names that encode parts of the sender address or other message-specific values. That can create privacy exposure and leave more message-derived detail in DNS logs than a static SPF design would.
For a lot of organizations, that is a bad trade without any meaningful deliverability benefit.
RFC 7208 also notes that syntactically invalid domain-spec results after macro expansion do not have perfectly uniform handling across implementations.
That is not the kind of sentence you want attached to your production authorization policy.
In plain terms, a macro can expand into something malformed or awkward enough that different evaluators do not all react in the same way. Even when receivers broadly follow the standard, the resulting behavior is harder to reason about than a static target name.
When teams reach for macros, the real need is often one of these:
Macros are rarely the cleanest answer.
Usually the better answer is one of these:
include: published by the providerredirect= when one domain should fully inherit another policyThat is the same general lesson behind SPF "flattening" vs maintaining includes and SPF failure modes decoded: SPF becomes fragile when policy design tries to be too clever.
If you are wondering where admins actually run into them, the usual sources are pretty mundane:
So if a domain suddenly shows macros, that does not automatically mean the design is wrong.
It does mean the record deserves a review with a skeptical eye.
Ask:
include, redirect, ip4, or ip6 terms?If those questions do not have clean answers, that is usually the sign to simplify.
Suppose a domain publishes this:
example.com. IN TXT "v=spf1 exists:%{ir}.%{v}._spf.example.net -all"That can work. It can also send the next person debugging SPF down three different rabbit holes:
%{ir} expands%{v} contributes_spf.example.netCompare that with a simpler policy:
example.com. IN TXT "v=spf1 include:_spf.example.net -all"The second version is not automatically right for every architecture, but it is dramatically easier to review, test, and hand off.
That difference matters in the real world more than the theoretical flexibility of macros.
Do not rip them out blindly.
First, map what they expand into and what DNS zone they depend on. Then confirm whether the macro path still corresponds to an actively used sending service.
Use a short review checklist:
exists, redirect, exp, or another macro-expandable target.include, redirect, or explicit IP mechanism can replace it.The migration goal should usually be boring SPF.
That is a compliment.
SPF macros are part of the standard, and they do have niche uses.
But for most domains, they are a complexity multiplier, not a deliverability advantage.
If a domain's outbound mail can be described with static includes, redirects, subdomain separation, and explicit IP authorization, that is almost always the better operational choice.
The simple rule is: