Seeing Received-SPF: softfail in a message header, or mail quietly landing in Junk with no bounce? SPF checked the sending server against the domain's published record, did not find it, and the domain owner chose the weak enforcement setting. The message is not rejected — it is flagged.
The Error
In a raw message header:
Received-SPF: softfail (protection.outlook.com: domain of transitioning
example.com discourages use of 203.0.113.10 as permitted sender)
client-ip=203.0.113.10; helo=mail.vendor.example;
Or in Authentication-Results:
Authentication-Results: mx.google.com;
spf=softfail (google.com: domain of transitioning user@example.com
does not designate 203.0.113.10 as permitted sender)
smtp.mailfrom=user@example.com;
dkim=pass header.i=@example.com;
dmarc=pass (p=NONE) header.from=example.com
The phrase "domain of transitioning" is the giveaway — several receivers use that wording specifically for ~all results. The client-ip= value is the address that was evaluated, and it is the most useful field in the whole header.
Why This Happens
An SPF record ends with an enforcement mechanism that tells receivers what to do about servers not listed:
| Mechanism | Result | Meaning |
|---|---|---|
-all | fail (hardfail) | Not authorised. Reject. |
~all | softfail | Probably not authorised, but do not reject on this alone. |
?all | neutral | No statement. Testing only. |
+all | pass | Everyone is authorised. Never publish this. |
RFC 7208 §8.5 defines softfail precisely:
A "softfail" result ought to be treated as somewhere between "fail" and "neutral"/"none". The ADMD believes the host is not authorized but is not willing to make a strong policy statement. Receiving software SHOULD NOT reject the message based solely on this result, but MAY subject the message to closer scrutiny than normal.
That "closer scrutiny" is why softfail is more dangerous than hardfail in one respect: a hardfail bounces, and somebody notices. A softfail delivers to the spam folder, and nobody notices for weeks.
So a softfail always means the same thing: a server is sending as your domain and is not in your SPF record. Either it is legitimate and needs adding, or it is not and you have just found a spoofing attempt.
Fix 1: Identify the Sending IP
Take client-ip= from the Received-SPF header and work out what it is. Our email header analyzer will parse the full header block and show the evaluated IP alongside the SPF, DKIM and DMARC results.
Then check what that address belongs to:
# Reverse DNS often names the vendor outright
dig +short -x 203.0.113.10
You will land in one of two places:
- You recognise it — a marketing platform, CRM, ticketing system, scanner, or an application server. Go to Fix 2.
- You do not recognise it — someone is sending mail as your domain. Do not add it. Go to Fix 4.
Fix 2: Add the Legitimate Sender to Your SPF Record
Every service that sends mail using your domain in the envelope sender must appear in your one SPF record. Get the correct mechanism from the vendor's own documentation — do not guess, and do not copy an IP you saw in a header, because most vendors send from a range that changes.
A Microsoft 365 domain that also sends through a bulk platform and one on-premises server:
v=spf1 ip4:203.0.113.10 include:servers.bulkvendor.example include:spf.protection.outlook.com -all
Rules that matter:
- Exactly one SPF record per domain. Two records produce a permerror and everything fails. Merge them into one record with multiple mechanisms.
- Stay under 10 DNS lookups.
include:,a,mx,existsandredirecteach cost one, counted recursively through nested includes. Exceeding it produces a permerror, which is worse than the softfail you started with. - Watch the syntax. Microsoft documents three recurring mistakes: a trailing period (
include:spf.protection.outlook.com.), an equals sign instead of a colon (include=), and a space after the colon (include: spf...). Each one breaks the record.
Our SPF record generator will assemble a syntactically valid record from your list of senders.
Fix 3: Decide Between ~all and -all
Once every legitimate sender is listed, the softfail becomes a hardfail candidate.
- Keep
~allwhile you are still discovering senders. It is the safe default during migration and audit, because an omission degrades deliverability rather than bouncing mail. - Move to
-allonce DMARC aggregate reports show every legitimate source passing for several weeks. Microsoft recommends-allfor Microsoft 365 domains, on the grounds that it lets DMARC act decisively on messages that fail SPF and carry no DKIM signature.
Do not move to -all on the strength of "I think that's all of them". Move on the strength of the reports.
Fix 4: If You Do Not Recognise the Sender
A softfail from an IP you cannot account for is SPF doing its job. Do not add it to your record.
-
Check the message's
From:header and body — if it is a phishing attempt using your brand, treat it as a security incident. -
Confirm your record ends in
~allor-allrather than?allor nothing. -
Publish DMARC with aggregate reporting so you can see the full picture of who is sending as your domain:
v=DMARC1; p=none; rua=mailto:dmarc@example.comp=nonechanges no delivery behaviour; it only turns on reporting. Read the reports, fix the legitimate sources, and only then tighten top=quarantineand eventuallyp=reject. Our DMARC generator builds the record.
Going straight to p=reject before the reports are clean is the standard way this improvement becomes an outage.
What Not To Do
Never change ~all to +all. It makes SPF return pass for every server on the internet, which is not a fix but a removal of the control. Spoofed mail claiming to be from your domain will authenticate successfully, your DMARC policy becomes meaningless, and receivers that notice will treat the domain as untrustworthy.
Do not delete the SPF record to stop the softfail. No record means no SPF result at all, DMARC loses one of its two inputs, and deliverability gets worse rather than better.
Do not add every IP you see in a header. Vendor sending IPs rotate. Use the include: mechanism the vendor documents, so their changes propagate automatically.
Verify the Fix
Wait for the old record's TTL to expire, then confirm what is published:
dig +short TXT example.com | grep spf1
You should see exactly one line beginning v=spf1. If you see two, that is a permerror and must be fixed by merging them.
Then send a live test from the system that was failing, to an external mailbox you control, and read the header. In Outlook on the web use View message details; in Gmail use Show original. You want:
spf=pass (sender IP is 203.0.113.10) smtp.mailfrom=example.com
Test from every sending system separately. Receivers evaluate mechanisms left to right and stop at the first match, so one system passing tells you nothing about the others.
Prevention
- Add SPF review to your vendor onboarding checklist. A new service that sends as your domain is the single most common cause of a new softfail.
- Send bulk mail from a subdomain such as
marketing.example.comwith its own SPF record. Each subdomain gets its own 10-lookup budget, and a campaign's reputation cannot damage your transactional mail. - Set a TTL of at least 3600 seconds on the SPF record as Microsoft recommends, but drop it temporarily before a planned change so the update takes effect quickly.
- Enable DKIM as well as SPF. DKIM survives forwarding while SPF does not, so it is what keeps forwarded mail authenticating.
- Keep DMARC aggregate reports flowing to a monitored mailbox and read them monthly rather than only when something breaks.
Summary
- Softfail =
~all+ an unlisted sender. Mail is flagged, usually junked, rarely bounced - Read
client-ip=from theReceived-SPFheader to identify the server - Add legitimate senders to your single SPF record using the vendor's documented
include: - Keep it to one record and under 10 lookups, and watch for trailing dots and stray spaces
- Tighten
~allto-allonly once DMARC reports show every source passing - Never use
+all— it authorises the entire internet to send as your domain