egressif.

Resources / Authentication

SPF Deep Dive (RFC 7208)

SPF authorizes which hosts may use your domain in the SMTP envelope. This is the full RFC 7208 picture: record format, every mechanism and qualifier, the hard 10-lookup limit that breaks records, the result codes, and the mistakes that quietly cause permerror.

Last checked: June 21, 2026

SPF (Sender Policy Framework) is the oldest of the three core authentication mechanisms and the one most often misunderstood. It does exactly one thing: it lets the owner of a domain “explicitly authorize the hosts allowed to use their domain names” in the MAIL FROM or HELO identities, and lets a receiver check the connecting IP against that authorization (RFC 7208 §1). That is the whole job. It says nothing about the From: header a person reads, and nothing about whether the mail is wanted.

The 60-second version

  • One DNS TXT record per domain, starting with v=spf1. Published as TXT (type 16) only - the old SPF RR (type 99) is discontinued (RFC 7208 §3.1).
  • Exactly one SPF record per domain. Two matching records is not “belt and suspenders,” it is a permerror (RFC 7208 §3.2).
  • Mechanisms are read left to right; the first match wins, and its qualifier (+ - ~ ?) decides the result.
  • There is a hard ceiling of 10 DNS-querying terms per evaluation. Exceed it and you get permerror (RFC 7208 §4.6.4).
  • SPF validates the envelope, not the visible From:. For DMARC, only the MAIL FROM identity counts, and it must align with From: (RFC 9989 §3.2.4, §4.4.2).
  • SPF breaks on forwarding by design, because the forwarder’s IP is not in your record.
SMTP CONNMAIL FROMclient IPRECEIVERverifierDNS TXT LOOKUPv=spf1 … -allEVALUATEmechanisms L to RMAX 10 DNS LOOKUPSPASSSOFTFAILFAIL
The receiver fetches the SPF TXT record and evaluates mechanisms left to right; exceeding ten DNS-querying terms is a permerror.

Record format

An SPF record is a single DNS TXT record. The version section is v=spf1 and is terminated by a space or end of record; a record beginning v=spf10 does not match and is discarded (RFC 7208 §4.5).

example.com.  3600 IN TXT  "v=spf1 ip4:198.51.100.0/24 include:_spf.provider.example -all"

Two formatting rules trip people up:

  • One record only. “A domain name MUST NOT have multiple SPF records that would cause an authorization check to select more than one record.” Two v=spf1 records produces permerror (RFC 7208 §3.2, §4.5).
  • Long records are split into strings, concatenated with no space. A single TXT record may hold multiple character-strings; they are joined with nothing between them (RFC 7208 §3.3):
; these two strings...
IN TXT "v=spf1 ip4:198.51.100.0/24 " "include:_spf.provider.example -all"
; ...are identical to:
IN TXT "v=spf1 ip4:198.51.100.0/24 include:_spf.provider.example -all"

Keep the record small: it SHOULD fit within 512 octets, and DNS messages under 450 octets fit in a single UDP packet (RFC 7208 §3.4).

Qualifiers

Every mechanism may carry a qualifier. It is optional and defaults to + (RFC 7208 §4.6.2).

QualifierSymbolResult when the mechanism matches
Pass+the client is authorized
Fail-the client is not authorized
SoftFail~the host is probably not authorized (a weak statement)
Neutral?no assertion about authorization

The practical upshot: -all is a hard “nobody else,” ~all is “probably nobody else but I’m not certain,” and ?all asserts nothing. If no mechanism matches and there is no all, the result is neutral (an implicit ?all).

Mechanisms

Mechanisms are evaluated left to right and defined in RFC 7208 §5.

MechanismSyntaxMatches whenDNS lookup?
allallalways (use as the rightmost default)no
includeinclude:domainthe result of evaluating the referenced domain is passyes
aa / a:domain / a:domain/cidrthe client IP matches an A/AAAA record of the domainyes
mxmx / mx:domain / mx:domain/cidrthe client IP is one of the domain’s MX hostsyes
ptrptr / ptr:domainreverse DNS of the IP resolves within the domainyes
ip4ip4:net[/cidr]the client IP is in the IPv4 range (default /32)no
ip6ip6:net[/cidr]the client IP is in the IPv6 range (default /128)no
existsexists:domaina macro-built domain returns any A recordyes

A few subtleties from the spec:

  • all always matches and is the explicit default. Mechanisms listed after all MUST be ignored, and any redirect modifier MUST be ignored whenever all appears in the record (RFC 7208 §5.1).
  • include is misleadingly named. It does not splice in the other record’s mechanisms; it runs a full recursive check and uses only the result. pass makes the mechanism match; fail/softfail/neutral do not match; temperror/permerror propagate; and a none result from the included domain returns permerror (RFC 7208 §5.2).
  • mx does the MX lookup then A/AAAA on each MX host, and a single mx evaluation must not query more than 10 address records or it returns permerror. Implicit MX rules MUST NOT be applied (RFC 7208 §5.4).
  • ptr SHOULD NOT be published - it is slow, unreliable under DNS errors, and burdens .arpa name servers. Implementations must still support it, but you should not use it (RFC 7208 §5.5).

Modifiers

Modifiers are name=value pairs, not mechanisms, and do not directly produce a match (RFC 7208 §6).

  • redirect=domain - if no mechanism matches and there is no all, evaluation continues with the referenced domain’s record. If that domain has no SPF record, the result is permerror (not none). redirect is ignored if all is present anywhere (RFC 7208 §6.1).
  • exp=domain - on a fail caused by a mechanism match, an explanation TXT string is fetched and macro-expanded; it MUST be US-ASCII (RFC 7208 §6.2).

The 10-lookup limit (the thing that breaks real records)

This is the single most common operational SPF failure. SPF implementations MUST limit the total number of DNS-querying terms to 10 during evaluation. Exceed it and the result is permerror (RFC 7208 §4.6.4).

Counts toward the 10Does NOT count
includeall
aip4
mxip6
ptrexp modifier
exists
redirect modifier

So ip4/ip6 are free; include is expensive. Each include you add costs at least one lookup, plus whatever lookups that included record performs - the count is cumulative across the whole recursive evaluation, not per record. A handful of vendor include: statements, each chaining its own includes, is how a record silently crosses 10 and turns every check into permerror.

Two more limits in the same section (RFC 7208 §4.6.4):

  • Void lookups. Queries that return RCODE 0 with zero answers, or NXDOMAIN, SHOULD be limited to two (a default of two is RECOMMENDED). Exceeding produces permerror. Records pointing at include: or a:/mx: targets that no longer exist quietly burn this budget.
  • Time limit. A processor SHOULD allow at least 20 seconds of elapsed time for the check; if it runs longer, the result SHOULD be temperror.

Results of evaluation

Defined in RFC 7208 §2.6.

ResultMeaning
NoneNo valid domain to check, or no SPF record was found.
NeutralThe domain explicitly asserts nothing (? qualifier or implicit ?all).
PassThe client is authorized for this identity.
FailThe client is not authorized for this identity.
SoftFailWeak statement that the host is probably not authorized.
TemperrorA transient (usually DNS) error; a retry may succeed.
PermerrorThe published records could not be correctly interpreted; needs operator action.

The distinction that matters operationally: temperror is “try again,” permerror is “your record is broken.” If your monitoring shows permerror, the cause is almost always the 10-lookup limit, void lookups, or two SPF records.

Why SPF breaks on forwarding

SPF authorizes IP addresses. When a message is forwarded - by a .forward rule, a mailing list, or any relay - the message reaches the final receiver from the forwarder’s IP, which is not listed in your domain’s SPF record. The receiver checks that IP against your record, finds it unauthorized, and SPF fails for your domain (this follows directly from the IP-versus-domain check in RFC 7208 §2.2).

This is not a bug in your record; it is inherent to how SPF works. It is the reason DMARC accepts DKIM as an alternative, and the reason ARC exists. DKIM, being a signature over content rather than a check on the connecting IP, can survive a plain forward where SPF cannot.

SPF and DMARC alignment

For DMARC, SPF is not used the way it is for raw SPF checks:

  • DMARC relies solely on SPF validation of the MAIL FROM identity (RFC5321.MailFrom). SPF validation of HELO is not used by DMARC (RFC 9989 §3.2.4, §4.4.2).
  • A bare SPF pass is not enough for DMARC. The validated MAIL FROM domain must also be in alignment with the From: (Author) domain (RFC 9989 §4.4.2).

This is why an ESP can show “SPF: pass” while your DMARC still fails: the pass is on the ESP’s bounce/return-path domain, which does not align with your visible From:. To make SPF contribute to DMARC, the MAIL FROM domain has to share your Organizational Domain (relaxed) or be identical (strict).

Common mistakes / what does NOT change

  • Two SPF records. Publishing a second v=spf1 TXT record (often when adding a new provider) is permerror, not additive. Merge into one (RFC 7208 §3.2).
  • +all or a trailing all with +. +all authorizes the entire internet to send as your domain. It is the functional opposite of protection.
  • ptr. Do not publish it (RFC 7208 §5.5).
  • Treating SPF as anti-spoofing for the From: header. It validates the envelope, full stop. The visible-From: protection comes from DMARC, layered on top.
  • Assuming SPF survives forwarding. It does not, by design.
  • Over-stuffed include: chains. Every nested record’s lookups count toward your 10. Audit the full expansion, not just the top-level record.

What Egressif does

We keep your SPF record to a single, lookup-conscious TXT entry that stays well under the 10-term ceiling, account for every legitimate sending source so the record does not need a permissive ~all/+all crutch, and bias toward authenticating with aligned DKIM so that DMARC still passes on the forwarded and relayed paths where SPF cannot. We monitor for permerror and temperror in DMARC aggregate reports, because a record that crossed the lookup limit fails silently - mail keeps flowing while the SPF half of DMARC quietly stops counting.

Related references

Tell us what you run today.

Domains, rough volume, current providers, and what hurts. You will get a straight answer on fit, and a real number, in one conversation.

Talk to our team