Skip to content
Control Plane Labs

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.

Other record types