To set up an SPF record, list every server and service that sends mail as your domain, combine them into one DNS TXT record at the domain root that starts with v=spf1 and ends with -all (or ~all while you are still checking), and publish it. Then test it with dig and a real message. That is the whole job; the rest of this guide is about getting each part right.
SPF (Sender Policy Framework, RFC 7208) lets a receiving server check whether the IP that delivered a message is allowed to send for the domain in the envelope sender (the MAIL FROM, also called the Return-Path). It does not look at the visible From header. That gap is why SPF is only one of three pieces, alongside DKIM and DMARC.
We use example.com and the documentation IP 203.0.113.10 throughout.
Step 1: Inventory everything that sends as your domain
This is the step people skip, and it is where most broken SPF records come from. Check:
- Your own mail server, if you run one.
- The service your app uses for password resets and receipts.
- Your newsletter or marketing tool.
- Your helpdesk, CRM, invoicing and calendar tools, if they send "from" your domain.
- Your office mailbox provider.
For each one, find out whether it sends with your domain in the envelope sender or with its own. Many providers use their own bounce domain by default, in which case their SPF include belongs on that domain, not yours. Their documentation will tell you which, and usually gives the exact include: value.
If you are not sure what is sending, look at a few real messages: open them in Gmail, choose "Show original" and read the Return-Path and Authentication-Results headers. Our email checker also shows what your current DNS already says.
Step 2: Build the record
An SPF record is a list of mechanisms read left to right. The first match wins.
| Mechanism | Meaning | Counts toward 10 lookups? |
|---|---|---|
ip4:203.0.113.10 |
This IPv4 address (or range, e.g. /29) may send |
No |
ip6:2001:db8::/32 |
This IPv6 range may send | No |
a |
The IPs in this domain's A record may send | Yes |
mx |
The IPs of this domain's MX hosts may send | Yes |
include:spf.provider.example |
Whatever that domain's SPF allows may send | Yes, plus its own lookups |
exists, ptr, redirect= |
Advanced; ptr is discouraged by RFC 7208 |
Yes |
-all / ~all |
Everything else fails / softfails | No |
For a domain that only sends from its own server, this is our real record shape:
v=spf1 ip4:203.0.113.10 -all
If your app also sends through a provider:
v=spf1 ip4:203.0.113.10 include:spf.provider.example -all
Prefer ip4: for servers you control. It is exact and costs no DNS lookups. Use include: for providers, because their IP ranges change and they maintain the list for you.
-all or ~all?
-all means "anything not listed is not authorized". ~all means "probably not authorized, treat with suspicion". Start with ~all if you are unsure your inventory is complete, watch your DMARC reports for a couple of weeks, then switch to -all. We use -all on our own domains. Never use +all, which authorizes the entire internet.
Step 3: Publish it in DNS
In your DNS provider, create (or edit) a TXT record:
| Field | Value |
|---|---|
| Type | TXT |
| Host / Name | @ (the domain root) |
| Value | v=spf1 ip4:203.0.113.10 -all |
| TTL | 3600 is fine; lower it to 300 while you are testing |
Most DNS panels add the quotation marks for you. If yours shows the value in quotes after saving, that is normal. If you typed quotes and it now shows doubled quotes, remove yours.
If an SPF record already exists, edit it. Do not add a second one.
Add SPF for your mail server's hostname too
If you run your own server, bounce messages and some system mail use the server's hostname itself as the envelope sender, for example MAILER-DAEMON@mail.example.com, or an empty sender with mail.example.com as the HELO name. Receivers then check SPF for mail.example.com. We publish this on the mail host:
| Type | Host | Value |
|---|---|---|
| TXT | v=spf1 a -all |
a means "the IP in this host's own A record", which is the server itself. Our self-hosted email server guide covers the rest of that DNS set.
Subdomains that never send
A subdomain without an SPF record has no SPF policy, which is not the same as "may not send". For a domain that should never send mail at all, publish:
v=spf1 -all
Do the same for parked domains you own but never send from. It stops them being used in spoofed envelope senders, and it costs nothing. Pair it with a DMARC record of v=DMARC1; p=reject on those domains.
SPF record examples for common setups
These cover most of the domains we look at. Replace the include values with the ones your providers document.
| Situation | Record |
|---|---|
| Own mail server only | v=spf1 ip4:203.0.113.10 -all |
| Own server plus an app email provider | v=spf1 ip4:203.0.113.10 include:spf.provider.example -all |
| Two servers, one IP range | v=spf1 ip4:203.0.113.10 ip4:203.0.113.16/29 -all |
| Office mailbox provider plus newsletter tool | v=spf1 include:spf.office.example include:spf.newsletter.example ~all |
| Domain that never sends | v=spf1 -all |
A common pattern for providers that send in bulk is to give them their own subdomain, for example news.example.com, with its own SPF record. That keeps the root record short and separates the reputation of marketing mail from your transactional mail. SPF too many DNS lookups explains how that also solves the lookup limit.
If you want one record to reuse another's policy entirely, redirect= does that: v=spf1 redirect=_spf.example.com. It is useful when many domains share one sender list. It counts as a lookup, and it is ignored if the record also contains an all mechanism.
Step 4: Understand how SPF fits with DMARC
DMARC passes on SPF only if SPF passes and the envelope sender's domain aligns with the From header domain. If your newsletter tool sends with its own bounce domain, SPF may pass for that domain while doing nothing for yours. That is fine as long as DKIM is signed with your domain. Most providers offer a "custom return path" or "custom bounce domain" option if you want SPF alignment too.
With aspf=s (strict, which we use) the domains must match exactly. With the default relaxed mode, bounce.example.com aligns with example.com.
What the SPF results mean
When you read Authentication-Results headers or a verifier report, you will see one of these results, defined in RFC 7208:
| Result | Meaning | Usual cause |
|---|---|---|
pass |
The IP is authorized | Working as intended |
fail |
The IP is explicitly not authorized (-all) |
A missing sender, or spoofing |
softfail |
Probably not authorized (~all) |
A missing sender, or spoofing |
neutral |
The domain makes no assertion (?all) |
A deliberately vague record |
none |
No SPF record found | The record is missing or on the wrong host |
temperror |
A temporary DNS problem | A DNS timeout; usually resolves itself |
permerror |
The record cannot be evaluated | Two records, a syntax error, or more than 10 lookups |
permerror is the one to fix urgently. Receivers may treat it like a fail, and DMARC treats it as no SPF pass at all.
Common mistakes
- Two SPF records. Adding a second
v=spf1TXT record when a new service asks you to "add this SPF record" causes a permerror, and SPF fails for every message. Merge them into one. - More than 10 DNS lookups. Every
include,a,mx,ptr,existsandredirectcounts, including the ones inside included records. Go over 10 and the result is permerror. See SPF too many DNS lookups for how to count and fix it. - Forgetting a sender. A forgotten helpdesk or invoicing tool fails SPF the day you switch to
-all. Do the inventory, then watch DMARC aggregate reports before tightening. - Using
ptr. RFC 7208 discourages it: it is slow, costs lookups and is unreliable. Useip4:instead. - Putting the record on the wrong host. Some panels append the domain automatically, so typing
example.comas the host createsexample.com.example.com. Use@for the root. - Expecting SPF to survive forwarding. It does not, unless the forwarder uses SRS. That is why we run postsrsd on our own forwarding server, and why DKIM matters.
How to verify it worked
Query the record directly:
dig +short TXT example.com
dig +short TXT mail.example.com
You should see exactly one string starting with v=spf1 for each name. To check it through a public resolver, which is what many receivers effectively see, add @1.1.1.1:
dig +short TXT example.com @1.1.1.1
Then send a real message from each of your senders to port25's verifier. If you run your own server, swaks makes this quick:
swaks --to check-auth@verifier.port25.com --from hello@example.com \
--server mail.example.com --port 587 --tls \
--auth LOGIN --auth-user hello@example.com
The reply lists the SPF result; you want pass. For a provider, send through it normally to the same address. You can also send to mail-tester.com or a Gmail account and check "Show original" for spf=pass in Authentication-Results.
If SPF passes but mail still goes to spam, the cause is somewhere else. Our why are my emails going to spam checklist walks through the rest.
Want someone to audit and fix SPF, DKIM, DMARC and PTR for your domain on whatever provider you use? That is our Deliverability Fix; prices are on the pricing page.