What an SPF record is
SPF stands for Sender Policy Framework. It is a single DNS TXT record published at the root of your domain, and it answers one question for any server that receives mail claiming to come from you: was this message sent from an IP address I have authorised?
A typical record for an organisation using both Google Workspace and Microsoft 365 looks like this:
v=spf1 include:_spf.google.com include:spf.protection.outlook.com -all
Read left to right, that says: this is SPF version 1; the servers listed by Google's record and Microsoft's record are authorised; anything else fails. The receiving server evaluates the mechanisms in order and stops at the first one that matches the sending IP.
What SPF actually checks
This is the part that causes most of the confusion in practice, and it is worth stating bluntly: SPF validates the envelope sender, not the From header your recipient reads.
Every email has two sender addresses. The envelope sender - also called the Return-Path or MAIL FROM - is used during the SMTP conversation and is where bounces go. The From header is the one displayed in the mail client. They are often the same, and nothing requires them to be.
SPF also breaks on forwarding. When a recipient auto-forwards your message, the forwarding server sends it onward from its own IP address, which is not in your record, so SPF fails at the final destination. This is not a misconfiguration; it is inherent to how SPF works, and it is the main reason DKIM matters, since a DKIM signature survives forwarding intact.
Mechanisms and qualifiers
A record is a version tag followed by a list of mechanisms, each optionally prefixed by a qualifier.
| Mechanism | Matches | Counts toward the lookup limit? |
|---|---|---|
| ip4: / ip6: | A literal IP address or CIDR range. | No |
| a | The IP addresses of the domain's A/AAAA records. | Yes |
| mx | The IP addresses of the domain's mail exchangers. | Yes |
| include: | Pulls in another domain's SPF record and evaluates it. | Yes, plus everything inside it |
| exists: | Matches if a lookup for the given domain resolves. | Yes |
| ptr | Reverse-DNS match. Deprecated - do not use it. | Yes |
| all | Matches everything. Always the last mechanism. | No |
Qualifiers set what a match means. There are four:
- + is Pass, and it is the default when no qualifier is written. include:example.com and +include:example.com are identical.
- - is Fail. -all means anything not previously matched should be rejected.
- ~ is SoftFail. ~all means treat it as suspicious but do not reject outright.
- ? is Neutral, which says nothing at all and is effectively the same as having no policy for that source.
The practical difference between -all and ~all is smaller than people expect, because most receivers now weigh the DMARC policy far more heavily than the SPF qualifier. Use -all once you are confident your sender inventory is complete; ~all is a reasonable place to sit while you are still finding senders.
Mistakes that silently break it
SPF fails quietly. There is no error message and no alert - mail simply starts landing in spam somewhere, weeks later, for some recipients. These are the failures worth checking for directly.
- Two SPF records on one domain. This is invalid per the specification and most receivers treat it as a permanent error, which means you effectively have no SPF at all. It usually happens when a new service adds its own record instead of merging into the existing one. Merge them into a single record with multiple include mechanisms.
- Ending with +all. This authorises the entire internet to send as your domain, which is worse than publishing nothing because it looks configured. It is occasionally added as a temporary fix during a delivery problem and then forgotten.
- Exceeding ten DNS lookups. The specification caps evaluation at ten querying mechanisms, and every nested include counts. Over the limit the receiver returns PermError, which is treated as no SPF at all - see the SPF 10 DNS lookup limit.
- Forgetting a sender. The invoicing system, the ticketing tool, the marketing platform someone in another department signed up for. DMARC aggregate reports are how you find these, which is why the monitoring stage exists.
- Publishing SPF on a domain that sends no mail, and nothing else. A parked domain should have v=spf1 -all plus a DMARC record at p=reject, otherwise it remains perfectly spoofable.
- Using ptr. It is deprecated, slow, and some receivers ignore it entirely. There is no situation where it is the right answer.
Treat SPF as one third of a set rather than a control in its own right. On its own it authorises servers and stops nothing a determined sender cannot work around; combined with DKIM and enforced by DMARC it ends domain spoofing. And none of the three affect an attacker who never forges your domain in the first place, which is most of them - the wider control set is in phishing tests for employees, and the reason authentication does not touch credential relay is covered in can phishing bypass MFA?.