Most real SPF records are mostly include. It is how a domain authorises a platform whose addresses it does not know and should not have to track. It also behaves in one way that surprises people, and that surprise is the source of a whole category of bugs.
What include actually does
include:other.example means: evaluate other.example’s SPF record against this same connecting IP, and if that evaluation produces a pass, treat this mechanism as a match.
The word "include" is misleading. Nothing is textually inserted. It is a function call that returns pass or not-pass, and only pass matters.
| Included record evaluates to | The include mechanism |
|---|---|
| pass | Matches. Evaluation of the outer record stops here. |
| fail, softfail, neutral | Does not match. Evaluation continues with the next term. |
| none (no record at the included domain) | The whole outer record becomes permerror. |
| permerror | The outer record becomes permerror. |
| temperror | The outer record becomes temperror. |
The one that catches people
An included record ending in -all does not cause the outer record to fail. It produces fail, which is simply "not a pass", so the include does not match and evaluation moves to the next term. You can safely include a platform whose record ends in -all, and nearly all of them do.
But an include pointing at a domain with no SPF record at all is a hard failure of your entire record — RFC 7208 §5.2. This is how a decommissioned vendor takes your SPF down months after you stopped using them.
Nesting
Included records contain includes of their own, and those are evaluated too. PayPal’s record is a clear example of deliberate nesting:
"v=spf1 include:pp._spf.paypal.com include:3ph1._spf.paypal.com include:3ph2._spf.paypal.com include:3ph3._spf.paypal.com include:3ph4._spf.paypal.com include:sendgrid.net include:aspmx.pardot.com ~all"
Tracing this by hand means fetching each included record in turn:
dig +short TXT pp._spf.paypal.com dig +short TXT sendgrid.net # ...and any includes those contain, recursively
Every one of those lookups counts against a limit, which is the subject of the next lesson and the reason SPF records cannot simply grow forever.
redirect, and how it differs
redirect= is a modifier, not a mechanism. It means: abandon this record and use that one instead, including its all.
v=spf1 redirect=_spf.example.net
- It is only used if no mechanism matched. A
redirectin a record that also containsallis dead code, becauseallalways matches first. - The target’s result becomes your result, including a fail. Unlike
include, you inherit the ending. - It is the right tool for shared policy. If fifty domains should all have identical SPF, give them each a one-term record redirecting to a single maintained record, and there is one place to edit.
Choosing between them
Use include to add a sender to a record you own. Use redirect to say that this domain’s policy is another domain’s policy. If you find yourself with a record containing both a redirect and an all, the redirect is doing nothing and someone has misunderstood one of them.