"SPF is broken" is not a fault report. This module turns one into a named failure with evidence, and the method matters more than any individual check because it works on problems you have not seen before.
Establish four facts before touching anything
| Fact | Where it comes from |
|---|---|
| The sending IP | The Received header of a real failing message, or an aggregate report row. |
| The envelope domain | Return-Path in a delivered message. This is what SPF was evaluated against — not the From address. |
| The result | Authentication-Results, or policy_evaluated in a report. |
| When | A record that was correct yesterday and is not today points somewhere different from one that never worked. |
Most SPF investigations go wrong at fact two
People check the SPF record of the domain in the From header. SPF never looks at that. If the envelope is a vendor’s domain, the vendor’s record was evaluated and yours is irrelevant to the failure — which changes who has to fix it.
Then branch on the result
permerror -> the record cannot be evaluated. Yours. Next lesson.
temperror -> transient DNS. Usually not yours. Lesson after that.
fail/softfail -> the record evaluated fine; this host is not in it.
Is the host legitimate?
yes -> a sender is missing from the record, or the
envelope domain is not the one you think
no -> the record is working. Nothing to fix.
neutral -> the record asserts nothing. Look for ?all.
none -> no record at that domain. Check you are querying the
envelope domain, not the From domain.Reproduce before you change
Evaluate by hand for the specific address. Fetch the record, walk the terms, spend the budget, and see what you get:
dig +short TXT <envelope-domain> | grep spf1 # the record actually in play dig +short TXT <each include target> # the tree dig +short MX <domain> # if mx is present, count the hosts
If your hand evaluation disagrees with the receiver’s result, one of your four facts is wrong — almost always the envelope domain. Go back rather than forward.
Change one thing
- Write the expected outcome down first. "This removes two lookups and takes us to 8" is checkable; "this should help" is not.
- Lower the TTL before you touch the record, not at the same time — module 8.
- Verify against the authoritative server first, then a public resolver. The first tells you what you published; the second what the world sees.
- Confirm with a real message. The record being correct and the mail passing are two claims, and only the second is the one anybody asked about.