What ~all, -all and ?all Actually Do in an SPF Record
The last few characters of your SPF record decide what happens to every server you did not list — which is to say, every server an attacker would use. Two of the four options mean "no policy at all", and one of them is the default when you leave the qualifier off.
By ShipReady
all is a mechanism that matches every sender, so it decides the result for any server your record did not otherwise authorise. Its qualifier is the policy: -all means fail (this server is not authorised), ~all means softfail (probably not authorised, accept but mark), ?all means neutral, and +all means pass. Neutral must be treated by receivers exactly as if you had published no SPF record at all, and +all explicitly vouches for every server on the internet. If your record ends in ?all or +all, or has no all at all, SPF is doing nothing for you.
The four qualifiers
Every SPF mechanism can carry a qualifier. In practice it is only ever interesting on all, because that is the one that catches everything you did not list.
| Written | Result | What it tells a receiver |
|---|---|---|
-all | Fail | An explicit statement that this server is not authorised to send for the domain. Receivers may reject the message outright. |
~all | SoftFail | A weak statement of non-authorisation. The message should be accepted but marked — in practice, weighted towards spam. |
?all | Neutral | No assertion either way. Receivers must treat this exactly as if no SPF record existed. |
+all | Pass | Every server on the internet is authorised to send mail as you. This is almost never intended. |
The two rows worth dwelling on are the bottom two, because they are the ones that look like a policy and are not.
Neutral is indistinguishable from having no record
RFC 7208 §2.6.2 is unusually direct about this: a neutral result "MUST be treated exactly like the None result". A record ending ?all authorises your own senders and says nothing whatsoever about anyone else. Every SPF checker will report the record as valid, because it is — it just does not do the job you published it for.
A bare all means +all
This is the trap. The default qualifier is +, so a record ending in a bare all with no prefix resolves to a pass. v=spf1 include:_spf.google.com all does not mean "and that is the end of the list" — it means "and everyone else is fine too". Your record now actively vouches for the server sending forged mail as you, which is worse than publishing nothing at all. ShipReady rates an SPF record that authorises any server as high severity for exactly that reason.
No all at all
If evaluation runs off the end of the record without matching anything, the result is neutral — the same as ?all. A record with no terminal mechanism therefore authorises your senders and leaves everyone else unaddressed. ShipReady reports this as an SPF record that says nothing about servers it does not list, separately from the permissive-all case, because they read differently even though they land in the same place.
The exception is a record ending in a redirect= modifier, which hands evaluation to another domain’s record. That is a legitimate structure and the redirected record supplies the terminal mechanism.
So: ~all or -all?
This is the actual question people arrive with, and the honest answer is that it matters less than it used to — provided you have DMARC.
On its own, SPF is a poor basis for rejecting mail, because it breaks on forwarding. When a recipient auto-forwards your message, the forwarding server connects from an IP your record does not list, and SPF fails through no fault of yours. A -all invites receivers to reject that legitimate message.
DMARC changes the calculation. It evaluates whether an aligned SPF or an aligned DKIM passed, and DKIM survives forwarding. So with DKIM signing in place and a DMARC policy at p=reject, forwarded mail still authenticates via DKIM, and the SPF qualifier stops being the thing standing between a forged message and an inbox.
| Your setup | Reasonable ending | Why |
|---|---|---|
| SPF only, no DKIM or DMARC | ~all | You have no second mechanism, so a hard fail will lose legitimate forwarded mail. |
SPF + DKIM + DMARC at p=none | ~all | You are still in the observation phase. Do not tighten SPF before you understand your own sending. |
SPF + DKIM + DMARC at p=reject | ~all or -all | Either is defensible. DMARC is doing the enforcing; -all adds a signal for receivers that evaluate SPF standalone. |
| A domain that sends no mail at all | -all | There is no legitimate mail to break. Publish v=spf1 -all and a strict DMARC record. |
If you want one rule: use ~all while you are setting things up, and treat getting to DMARC `p=reject` as the goal rather than tightening SPF. A hard SPF fail on a domain with no DMARC is a weaker position than a soft fail on a domain with enforcement.
Reading a record end to end
v=spf1 include:_spf.google.com include:sendgrid.net ip4:203.0.113.10 ~allv=spf1— the version. Required, and it is how receivers tell an SPF record from every other TXT record on the domain.include:_spf.google.com— if Google’s record would pass this sender, pass. Costs at least one DNS lookup.include:sendgrid.net— likewise for SendGrid.ip4:203.0.113.10— authorise this address directly. Costs no lookup.~all— everything else softfails.
Mechanisms are evaluated left to right and the first match wins, which is why all must be last: anything after it is unreachable.
The limit that breaks records quietly
SPF evaluation may perform at most ten DNS lookups (RFC 7208 §4.6.4). include:, a, mx, ptr, exists and redirect= each cost one, and an include: that itself contains includes costs the sum. Exceed ten and evaluation returns permerror — which most receivers treat as no usable SPF result, making your carefully chosen all qualifier irrelevant.
It creeps up on you: four providers, each with a nested include, and you are over. When you hit it, remove providers you no longer use before doing anything cleverer — most domains have an include: for a service they stopped paying for two years ago. ShipReady reports an SPF record over the lookup limit, and a syntax error when the record does not parse at all.
Check yours
dig +short TXT yourdomain.com | grep spf1- Exactly one line should come back. Two SPF records disable SPF entirely.
- Read the last mechanism. If it is
?all, a bareall, or missing, that is the finding in this article. - Count the lookups if the record has more than three or four
include:entries.
How ShipReady checks it
ShipReady resolves the SPF record for the domain you scan and evaluates its contents rather than its existence: whether it ends in a qualifier that resolves to a pass, whether it has no terminal mechanism, whether it parses, whether it stays under the lookup limit, and whether more than one record is published.
It reads public DNS only, the same answers any receiving mail server gets. Scan your domain free for the whole Email pillar, or start with what SPF, DKIM and DMARC each prove if the three records still blur together.
Frequently asked questions
- Will changing `~all` to `-all` break my email?
- It can, if any legitimate source is missing from your record or your mail gets forwarded. Move to `-all` only after DMARC aggregate reports have shown you every source that sends as you, and after DKIM is signing.
- Is `+all` ever correct?
- No. It is an explicit statement that any server may send as your domain, which makes SPF vouch for forgeries. It usually appears by accident, from a bare `all` or from copied advice about "fixing" delivery failures.
- Does the `all` qualifier matter if I have DMARC at p=reject?
- Much less. DMARC does the enforcing, and it needs an aligned pass from SPF or DKIM. The qualifier still matters for receivers that evaluate SPF on its own, so `~all` remains the sensible floor.
- What about `exp=`?
- It names a domain holding an explanation string for a fail result, which some receivers include in the rejection message. It is optional, costs a lookup only when a fail actually occurs, and does not affect the policy.