A complete SPF record is one TXT record at your domain's apex, containing a version tag, a list of mechanisms naming the servers allowed to use your domain as the envelope sender, and a default for everything else:
v=spf1 include:_spf.google.com include:spf.protection.outlook.com ~all
That is the whole format. A generator assembles this line from checkboxes, which is useful as a typo-check and useless for the two things that actually break SPF records: the DNS lookup budget, and knowing which services still send as you.
Mechanisms and what each one costs
| Mechanism | Authorises | DNS lookups |
|---|---|---|
include:domain | everything that domain's SPF record authorises | 1, plus everything inside it |
a or a:host | the A/AAAA records of the domain or host | 1 |
mx or mx:domain | the hosts in those MX records | 1 |
ip4:203.0.113.9 | one address | 0 |
ip4:203.0.113.0/24 | a CIDR range | 0 |
ip6:2001:db8::/32 | an IPv6 range | 0 |
exists:domain | a macro-expanded existence check | 1 |
ptr | hosts whose reverse DNS matches — deprecated | 1, slowly |
redirect=domain | replaces this record entirely | 1 |
all | everything not already matched | 0 |
Each mechanism carries a qualifier that tells the receiver what to do when it matches:
+pass — the default, somxand+mxare identical.~softfail — accept but mark. Correct while you are still discovering senders.-hardfail — reject. Correct once DMARC reports show nothing legitimate is missing.?neutral — explicitly no opinion. Almost never useful.
~all and -all are the only sensible endings. +all authorises the entire internet to send as you and some receivers score it as a spam signal on its own.
The 10-lookup limit, counted properly
SPF permits a maximum of 10 DNS-querying mechanisms per evaluation. The count is recursive: each include costs one, plus every lookup inside the record it points at, plus every lookup inside *those*. Exceeding it returns PermError, and PermError means SPF fails for all your mail at once — not partially, not just for the last include.
There is a second, less-known limit: at most two "void" lookups, meaning queries that return no answer. An include pointing at a service that no longer exists is not merely useless; two of them put you over a limit.
Count by hand. Expand each include:
dig +short TXT _spf.google.com dig +short TXT spf.protection.outlook.com
A record containing further includes contributes all of them. Large providers commonly cost three or four lookups each, which is why four ESP includes are usually over budget before you add your own mx and a.
How to get back under it, in the order to try:
- Remove services you no longer use. This fixes most over-budget records on its own.
- Delete `a` and `mx` if those hosts do not send mail. They are the most common dead weight — your web server and your inbound mail hosts usually send nothing.
- Delete `ptr`. Deprecated, slow, and ignored by some receivers.
- Move services to their own subdomains.
mg.yourdomain.comfor Mailgun,news.yourdomain.comfor marketing. Each subdomain gets its own record and its own fresh budget of 10, and the apex keeps room for your mailbox provider. - Only then consider flattening includes into
ip4:ranges. Flattening works and costs nothing in lookups, but you have just taken permanent responsibility for tracking a provider's IP changes. A stale flattened record fails silently, which is worse than a record that fails loudly.
The rules that invalidate a record
- One `v=spf1` record per name. Two is a specification violation: receivers return
PermErrorand ignore SPF entirely. This is the most common broken-record case and it usually happens when two teams each add "their" record. - `v=spf1` must come first. No leading whitespace, no other tag before it.
- One record, split into quoted strings if long. A TXT string is limited to 255 characters; a long record is published as multiple quoted strings in the same record and concatenated by the resolver. Two separate records is a different, broken thing.
- No nested `redirect` and `all` together.
redirect=is only consulted when no mechanism matched, and anallmechanism always matches, so a record containing both never reaches the redirect. - SPF belongs at the apex and at any subdomain that sends. Subdomains do not inherit the apex record. A subdomain with no SPF record of its own has no SPF, which is why a
v=spf1 -allrecord on non-sending subdomains is worth publishing.
Audit an inherited record in five minutes
dig +short TXT yourdomain.com | grep spf1 # exactly one line expected dig +short TXT yourdomain.com @8.8.8.8 # compare against a public resolver dig +short TXT <each include target> # expand and count
Then ask, for each mechanism: which service is this, and does it still send as us? Anything you cannot name should be removed — after checking DMARC reports to confirm no legitimate traffic depends on it.
What SPF cannot do for you
SPF validates the envelope sender, not the From header your recipient sees. It breaks on forwarding, always, because the forwarder is not in your record. And a passing SPF check contributes nothing to DMARC unless the authenticated domain *aligns* with the From domain — so a platform sending with its own bounce domain gives you a pass that DMARC ignores. For hosted sending, aligned DKIM is usually what carries DMARC, and SPF is the backup.
What not to do
- Do not add every provider's include "to be safe". Count first.
- Do not use a generator's output without expanding the includes and counting lookups.
- Do not publish a second SPF record. Merge, always.
- Do not leave
ptror stale includes in place. - Do not set
-allbefore DMARC reports show you have found every sender. Go~allfirst. - Do not treat SPF as the whole job. Publish DKIM and DMARC too; SPF alone is the weakest of the three.
MailPilot checks SPF, DKIM and DMARC on every connected mailbox's sending domain and re-checks as you send, so a record that drifts over the lookup limit or picks up a duplicate shows up as a failing check instead of as a slow decline in placement.
Ready to reach the inbox every time?
Automated email warmup across real peer mailboxes. Live inbox placement monitoring. Free 7-day trial - no credit card required.
Start free trial