There is a second modifier almost nobody publishes. exp= lets a domain supply the text a receiver may show when it rejects a message for failing SPF. It is worth ten minutes because knowing it exists explains a class of bounce message you will otherwise find inexplicable.
How it works
exp= names a domain. When an evaluation ends in fail, the receiver may look up a TXT record at that name, expand any macros in it, and include the result in its rejection (RFC 7208 §6.2).
example.com. TXT "v=spf1 ip4:198.51.100.0/24 exp=why.example.com -all"
why.example.com. TXT "%{i} is not authorised to send as %{d}. See https://example.com/mail"A receiver rejecting a message from 203.0.113.9 could then return:
550 5.7.1 203.0.113.9 is not authorised to send as example.com.
See https://example.com/mailThe rules that make it safe
- It only applies to
fail. Not softfail, not neutral, not the errors. - The lookup is outside the budget. It happens after evaluation has already finished, so it cannot push you into permerror.
- Receivers may ignore it entirely, and most do. Never rely on it being seen.
- Only one
exp=per record, and its position does not matter — it is a modifier, not a mechanism.
Why you probably should not publish one
The text is shown to whoever triggered the rejection, and that is very often not you — it is a stranger spoofing your domain, or an automated system. Anything you write there is returned to them. Do not put internal hostnames, ticket queues, staff names or a phone number in it.
The honest case for publishing one is a large sender whose legitimate partners occasionally get blocked and need somewhere to go. For everyone else it is a small information leak in exchange for a message most receivers discard.
Reading one you did not write
If a bounce quotes an oddly specific sentence about authorisation, it came from an exp= record and tells you the sending domain reached fail rather than any other result. That is a free diagnosis: you now know the record was evaluated successfully and your host is simply not in it — which rules out permerror, temperror and a missing record in one step.