SPF record
Lists the hosts allowed to send mail using a domain in the SMTP envelope sender.
Written and maintained by Ben Ennis
Last reviewed July 27, 2026 · How we verify this
Zone-file example
example.com. 3600 IN TXT "v=spf1 mx include:_spf.provider.net -all"Typical uses
- Authorising a hosted mail provider to send using your domain
- Authorising marketing, ticketing, and monitoring platforms that send on your behalf
- Publishing v=spf1 -all on names that never send mail
- Feeding the SPF half of DMARC alignment
When it breaks, check
- Only one v=spf1 record may exist per name; two is a permerror, not a merge
- Evaluation is capped at 10 DNS-querying mechanisms, and include: chains blow through it
- -all asks receivers to reject unlisted senders, ~all only marks them
- SPF checks the envelope sender, not the From: header a recipient sees
What the SPF record does
An SPF policy is a small program. Mechanisms are evaluated in order and the
first one matching the connecting address decides the result, with a trailing
all catching the rest. ip4: and ip6: match literal
addresses, a and mx match whatever those records resolve to, and
include: pulls in another domain’s policy wholesale.
The identity checked is the envelope sender from the SMTP MAIL FROM command,
not the From: address a human reads. A forwarded message keeps its
From: and acquires a new envelope sender, so SPF breaks on forwarding by
design. That gap is what DMARC alignment exists to close.
TXT record syntax
The value is one quoted string beginning v=spf1, followed by
space-separated mechanisms, each optionally prefixed with a qualifier:
+ pass, - fail, ~ softfail,
? neutral. A bare mechanism defaults to pass. There is no dedicated
record type any more: type 99 existed, and
RFC 7208 §3.1 deprecated it in favour of publishing in
TXT. Exactly one v=spf1 record may exist at a name; two
is a permanent error, not a union of the pair.
; mechanisms are evaluated left to right, first match wins
example.com. 3600 IN TXT "v=spf1 mx ip4:203.0.113.0/24 include:_spf.provider.net -all"
; a name that never sends mail
parked.example.com. 3600 IN TXT "v=spf1 -all"
; INVALID: two v=spf1 records at one name is a permerror, not a merge
; example.com. 3600 IN TXT "v=spf1 include:vendor-a.net ~all"
; example.com. 3600 IN TXT "v=spf1 include:vendor-b.net ~all"
Common uses
The realistic job of SPF is authorising the handful of platforms that send
as you: the mail provider, the transactional sender, the CRM, the ticketing system. Each
arrives with an include: to paste in, and the set grows quietly until
something breaks. Publishing v=spf1 -all on names that never send is the
higher-value half of the work, since a hard fail there has no false positives.
Troubleshooting
The most common real-world failure is the lookup limit.
RFC 7208 §4.6.4 caps evaluation at ten DNS-querying mechanisms —
include, a, mx, ptr,
exists, and the redirect modifier — counted across the whole
nested tree, not just the record you wrote. Exceed it and the result is permerror, which
many receivers treat as a failure, so adding one more vendor breaks mail that worked
yesterday. Count the expansion, not your own line.
The trailing qualifier is the other conscious choice. -all asks receivers to
reject unlisted senders and will bite on forwarded mail; ~all merely flags
them. Under DMARC the difference matters less than people assume, because alignment does
the real work.
# the policy itself
dig example.com TXT +short | grep -i 'v=spf1'
# expand every include: and count DNS-querying mechanisms against the limit of 10
dig _spf.provider.net TXT +short
# type 99 is deprecated; nothing should answer here
dig example.com TYPE99 +noall +answer
A second, stricter limit hides inside the same section: void lookups
The ten-lookup ceiling above counts every DNS-querying mechanism in the expansion, but
RFC 7208 §4.6.4 sets a separate, much tighter limit on a specific kind
of failure within that count: a “void lookup” is any query that comes back either a
positive answer with zero records, or a Name Error (NXDOMAIN). Implementations
should cap void lookups at two, and exceeding that cap is a permerror in its own
right, independent of whether you are anywhere near the ten-lookup total. Two stale
include: references pointing at vendors who have since deleted their SPF
records, or one typo’d domain in an a or mx mechanism, is
enough to trip this on its own.
This is the limit that makes SPF failures show up mysteriously after a vendor offboards
rather than the day you removed them from your policy. If you stop using a service but
never delete its include: line, that include still resolves each evaluation,
still counts toward the ten-lookup budget, and once the vendor tears down their own SPF
infrastructure it also starts counting as a void lookup — two forgotten, dead includes is
already the limit, with everything else in your policy still valid. The fix is routine
hygiene rather than redesign: every time you stop sending through a platform, remove its
include: the same day, rather than leaving it as an inert reference. Testing
tools that only warn about the ten-lookup total will not catch this; you need to
specifically check whether each referenced name still resolves.
The defining RFC
SPF is RFC 7208, a Proposed Standard obsoleting the earlier experimental RFC 4408. Section 3.1 deprecates the dedicated SPF resource record, type 99, and requires publication in a TXT record; section 4.6.4 sets the ten-lookup limit behind most production breakage, and section 8 defines each result code. The retired type is still listed, marked obsolete, in the IANA DNS Parameters registry. Check a policy with the DNS lookup tool.
Reference and tooling
Every record type and its assigned numeric value is listed in theIANA DNS Parameters registry. To query a live zone from the browser, use theDNS lookup tool. If the record you are chasing is TLS-related, the TLS inspector andCertificate Transparency lookup are usually the next two stops.