What is SPF and how does it work?
How the Sender Policy Framework decides which servers may send mail for your domain, how receivers evaluate it, and the mistakes that break it.
By Exmoor Software6 min read
SPF (Sender Policy Framework) is a DNS record in which a domain lists the servers that are allowed to send email on its behalf. When a message arrives, the receiving server looks up that list and checks whether the server that connected to it is on it. The current specification is RFC 7208, published in 2014.
SPF is one of the three mechanisms — with DKIM and DMARC — that mailbox providers use to tell real mail from forged mail. Since 2024, Gmail and Yahoo require it from anyone sending in volume, and Microsoft does the same for Outlook.com since May 2025.
What SPF actually checks
Every email has two sender addresses:
- the envelope sender, given in the SMTP
MAIL FROMcommand and later stored in theReturn-Pathheader. Bounces go here; - the From header, the address people see in their mail client.
SPF checks only the domain of the envelope sender (and, optionally, the name the server announced in HELO). It says nothing about the From address. If your newsletter service sends with an envelope sender like bounce-123@mailer.example-esp.net, SPF is evaluated for mailer.example-esp.net, not for your domain.
That is why SPF alone does not stop someone from putting your address in the From header. Connecting the two is the job of DMARC, which requires the SPF domain to align with the From domain.
What an SPF record looks like
An SPF record is a TXT record on the domain itself:
example.com. 3600 IN TXT "v=spf1 ip4:203.0.113.10 include:_spf.google.com -all"
Read from left to right, it says:
v=spf1— this is an SPF record (version 1, the only one).ip4:203.0.113.10— the server at this IPv4 address may send.include:_spf.google.com— so may every server that Google’s SPF record authorizes (used for Google Workspace).-all— every other server is not authorized.
The parts between v=spf1 and the end are mechanisms, each optionally preceded by a qualifier that decides the result when it matches:
| Qualifier | Result when the mechanism matches | Meaning |
|---|---|---|
+ (default) | pass | authorized |
- | fail | not authorized |
~ | softfail | probably not authorized |
? | neutral | no statement |
The mechanisms you will meet in practice:
| Mechanism | Matches when the sending IP… | DNS lookup? |
|---|---|---|
ip4:192.0.2.0/24, ip6:2001:db8::/32 | is in the given range | no |
a, a:host.example.com | is an address (A/AAAA) of the domain or host | yes |
mx | is an address of one of the domain’s mail servers | yes |
include:other.example | passes the SPF record of other.example | yes |
exists:… | (advanced, used with macros) | yes |
ptr | has reverse DNS within the domain — do not use | yes |
all | always matches; placed last | no |
There is also the redirect= modifier, which hands the whole evaluation to another domain’s record when nothing else matched. It is useful for many domains that share one policy.
How a receiver evaluates SPF
The receiving server runs a procedure called check_host() (RFC 7208, section 4):
- Look up the TXT records of the envelope sender’s domain and keep those that start with exactly
v=spf1. - If there are none, the result is none. If there is more than one, the result is permerror.
- Evaluate the mechanisms from left to right. The first one that matches determines the result.
- If nothing matches and there is a
redirect=, continue with the target’s record. Otherwise the result is neutral.
DNS problems along the way produce temperror; syntax errors and exceeded limits produce permerror. Receivers decide what to do with each result — many treat permerror like fail.
The 10-lookup limit
To protect DNS from runaway records, RFC 7208 (section 4.6.4) caps the number of mechanisms and modifiers that need DNS queries — include, a, mx, ptr, exists and redirect — at 10 per evaluation, counting everything inside nested includes. Exceed it and the result is permerror.
This limit is the most common reason a working SPF record suddenly breaks. Consider:
v=spf1 include:_spf.google.com include:spf.protection.outlook.com include:servers.mcsv.net include:sendgrid.net a mx -all
Six terms at the top level look harmless, but each provider’s record may contain further includes, and providers change their records without telling you. You can only see the total by following every include, which is what the SPF checker does.
A second limit applies to void lookups — DNS queries that return nothing (the name doesn’t exist or has no records). More than two of them should also produce permerror. They usually come from an old include or a: for a host that no longer exists.
To get back under the limit:
- remove includes for services you no longer use;
- replace
aandmxwithip4/ip6ranges for your own servers; - never use
ptr; - move high-volume or third-party sending to a subdomain (for example
news.example.com) with its own SPF record.
Common mistakes
Two SPF records. Adding a second v=spf1 record when you add a new email service is the classic error. With two records, every message fails with permerror. Merge them:
; wrong: two records
example.com. TXT "v=spf1 include:_spf.google.com ~all"
example.com. TXT "v=spf1 include:sendgrid.net ~all"
; right: one record
example.com. TXT "v=spf1 include:_spf.google.com include:sendgrid.net ~all"
+all or no all at all. +all authorizes every server on the Internet. A record without all ends with the default neutral result, which tells receivers nothing about unauthorized senders.
Forgetting a sender. Invoices from your accounting system, notifications from your web shop, the CRM — every system that sends with your domain as envelope sender must be covered, or its mail fails SPF.
Expecting subdomains to inherit SPF. They don’t. SPF is looked up for the exact domain in the envelope sender. If you send as noreply@shop.example.com, shop.example.com needs its own record.
Broken strings. A TXT string can hold at most 255 characters, so long records are split into several strings, which DNS joins without adding spaces (RFC 7208, section 3.3). A split right between two mechanisms merges them into one invalid term unless the first string ends with a space.
Typographic quotes. Records copied from a document often contain “curly quotes” or non-breaking spaces, which make them invalid.
SPF for domains that don’t send email
Parked domains and domains used only for a website are easy targets for spoofing, because nobody watches them. Publish a record that authorizes nobody:
parked.example. TXT "v=spf1 -all"
Combine it with a null MX and a DMARC record with p=reject so that receivers reject any mail claiming to come from the domain.
SPF and DMARC
For DMARC, SPF only counts when the envelope sender domain is aligned with the From domain: identical in strict mode, or within the same organizational domain in the default relaxed mode. If an email service sends with its own bounce domain, SPF passes for its domain and DMARC gains nothing from it. Most services let you set a custom return-path domain (often a CNAME such as bounce.example.com) to fix that. DKIM signing with your own domain is the other way to make DMARC pass — see SPF vs DKIM vs DMARC.
Checking your record
- Run the SPF checker on your domain. It validates the syntax, follows every include and shows the total lookup count.
- Send a message to an external mailbox, open the original and look for
spf=passin theAuthentication-Resultsheader — or paste the headers into the email header analyzer. - After any change, remember that resolvers may keep the old record until its TTL expires.