An SPF record is a DNS TXT record that lists every server authorized to send email for your domain. The format is always v=spf1 followed by one or more mechanisms (each identifying an allowed source) and a closing all tag that tells receiving mail servers what to do with everything else. Getting the syntax right matters: a malformed or over-budget SPF record produces a permerror, which causes authentication failures that push mail to spam regardless of your content or sending reputation.
An SPF record is a DNS TXT record with the shape v=spf1 [mechanisms] [qualifier]all. Mechanisms identify authorized sending sources; qualifiers (+, -, ~, ?) define what happens when a source matches or misses. Evaluation proceeds left to right and stops at the first match. The all mechanism, always last, catches everything that did not match earlier. The full specification is RFC 7208.
For the bigger picture on how SPF fits alongside DKIM and DMARC, see What Is Email Authentication? SPF, DKIM, and DMARC Explained.
The Anatomy of an SPF Record
Every valid SPF record follows this structure:
v=spf1 [mechanism ...] [qualifier]all
Here is a realistic record for a domain that sends via Google Workspace and one additional ESP, annotated token by token:
v=spf1 include:_spf.google.com include:mail.example-esp.com ip4:203.0.113.5 -all
│ │ │ │ │
│ └── mechanism: authorize └── mechanism: authorize └── mechanism: └── default:
│ Google's sending IPs the ESP's sending IPs single IP reject all
│ address others
└── version tag (required, always "spf1")
| Token | Role |
|---|---|
v=spf1 | Version tag. Required. Must be first. Only spf1 is defined. |
include:_spf.google.com | Mechanism: recursively checks Google’s own SPF record |
include:mail.example-esp.com | Mechanism: recursively checks ESP’s SPF record |
ip4:203.0.113.5 | Mechanism: authorizes one specific IPv4 address |
-all | Qualifier + mechanism: hard-fail anything not matched above |
Only one SPF TXT record is allowed per domain or subdomain. Per RFC 7208, Section 3.2, a domain “MUST NOT have multiple records that would cause an authorization check to select more than one record.” Two SPF records on the same domain returns permerror immediately.
SPF Mechanisms: What Each One Does
Mechanisms test whether the sending IP matches a given condition. RFC 7208, Section 5 defines eight mechanisms.
| Mechanism | What it matches | Counts toward 10-lookup limit? |
|---|---|---|
ip4: | A specific IPv4 address or CIDR range (e.g., ip4:192.0.2.0/24) | No |
ip6: | A specific IPv6 address or CIDR range | No |
a | The A/AAAA records of a domain (defaults to the sending domain) | Yes |
mx | The A records of the domain’s MX hosts | Yes |
include:domain | Recursively evaluates domain‘s SPF record | Yes |
exists:domain | Performs an A lookup on a constructed domain name | Yes |
ptr | Reverse-DNS hostname of the sending IP | Yes (avoid) |
all | Always matches; catches everything | No |
ip4 and ip6
The simplest mechanisms: list an IP address or CIDR block. They do not trigger DNS lookups and do not count toward the 10-lookup limit, making them the most efficient way to authorize dedicated sending IPs you control.
v=spf1 ip4:192.0.2.10 ip4:198.51.100.0/24 ip6:2001:db8::/32 -all
include
include:domain tells the evaluating server to fetch and evaluate domain‘s SPF record. If that check returns pass, the mechanism matches. This is how ESPs and cloud mail providers authorize senders: they publish their IP ranges under their own domain, and you add one include: pointing to it. Each include: costs one DNS lookup, and nested include: chains add more.
a and mx
a matches if the sending IP equals any A or AAAA record of the specified domain (or the current domain if no domain is given). mx matches if the sending IP belongs to any of the domain’s mail exchange hosts. Both trigger DNS lookups. Avoid mx if your MX hosts are separate from your sending infrastructure.
exists
exists:domain constructs a domain name and performs an A lookup. It is mainly useful for macro-based SPF records at scale. One lookup per use.
ptr (avoid)
ptr performs a reverse-DNS lookup on the sending IP, then a forward lookup to verify the hostname ends in the specified domain. RFC 7208, Section 5.5 is explicit: “This mechanism SHOULD NOT be published.” The reason given: “This mechanism is slow, it is not as reliable as other mechanisms in cases of DNS errors, and it places a large burden on the .arpa name servers.” Use ip4:, ip6:, or include: instead.
Qualifiers: Controlling What Happens on a Match
Every mechanism can be prefixed with a qualifier. The qualifier controls the result returned when that mechanism matches. Per RFC 7208, Section 4.6.2, + is the default when no qualifier is written.
| Qualifier | Result | What receiving servers do | Typical use |
|---|---|---|---|
+ | Pass | Accept the message | Default; rarely written explicitly |
- | Fail | Reject the message | Hard enforcement (-all) |
~ | SoftFail | Accept but flag (spam folder likely) | Testing; cautious enforcement (~all) |
? | Neutral | No specific action | Testing only; never use in production |
A record with no qualifier on a mechanism is identical to one with +:
# These two are equivalent:
v=spf1 include:_spf.google.com -all
v=spf1 +include:_spf.google.com -all
-all vs ~all: The Decision That Matters Most
The all mechanism always matches. Placing it last means it catches every sending source not authorized by an earlier mechanism. The qualifier you pair with all determines how aggressively unauthorized sources are treated.
-all (hard fail): The sending IP is not authorized. Receiving servers should reject the message outright. Microsoft Defender for Office 365 documentation recommends -all when you also deploy DKIM and DMARC, because DMARC can then act on SPF failures to enforce your policy.
~all (soft fail): The sending IP is probably not authorized. Receiving servers typically accept the message but route it to the spam or junk folder. This is the default recommendation from Google Workspace’s SPF setup guide. It leaves room for mailing lists, forwarding, and misconfigured legacy servers to get through during a transition.
?all (neutral): No action is specified. Receiving servers can do anything. This is suitable only for testing, not for production records.
Which should you choose? Start with ~all while you audit your sending sources and verify DMARC alignment. Once you are confident every legitimate sender is covered, switching to -all provides stronger enforcement. With DMARC in place, the practical difference narrows: DMARC applies the domain owner’s published policy (p=reject or p=quarantine) regardless of whether SPF returned fail or softfail. Without DMARC, -all is the only mechanism that can trigger a hard rejection at the SPF layer.
+all (pass all) is never appropriate in production. It authorizes every server on the internet to send mail as your domain.
The 10 DNS Lookup Limit
RFC 7208, Section 4.6.4 states: “SPF implementations MUST limit the total number of those terms to 10 during SPF evaluation, to avoid unreasonable load on the DNS.” Exceeding 10 lookups produces a permerror, which receiving servers treat as an authentication failure, not a temporary one.
The same section adds: “SPF implementations SHOULD limit ‘void lookups’ to two.” A void lookup is a DNS query that returns zero records (RCODE 0 with no answers) or an NXDOMAIN. Two void lookups exhausts the void-lookup budget and also produces a permerror.
What counts toward the 10-lookup limit
As Microsoft’s SPF documentation confirms, the following mechanisms each cost one lookup:
include:amxexistsredirect(a modifier, not a mechanism, but it counts)ptr(another reason to avoid it)
These do NOT count:
ip4:andip6:values- The
allmechanism
Nested include: chains multiply quickly. An include:_spf.google.com lookup resolves to a record that may itself contain additional include: statements, each adding to your total. The number of DNS lookups is not the same as the number of include: lines in your record.
A real over-budget scenario
A domain using five ESPs, each requiring one include:, and one provider whose record contains three nested include: statements, can reach 10 lookups before adding anything else. The evaluating server hits the limit, returns permerror, and every message from that domain fails SPF. Delivery drops immediately.
SPF flattening: the workaround and its tradeoffs
Flattening replaces include: mechanisms with the actual IP addresses they resolve to, reducing DNS lookups to zero (since ip4: and ip6: never count). The result is a long record with dozens of IP addresses instead of a short record with a few include: lines.
The tradeoff: cloud mail providers change their sending IP ranges without notice. A flattened record that was correct in January may fail in March when the provider rotates subnets. Microsoft’s own documentation explicitly warns against flattening include:spf.protection.outlook.com because “Microsoft’s sending infrastructure uses dynamic IP addresses that change frequently.” The same applies to any large cloud provider.
If you must flatten, document which include: entries you replaced, monitor the provider’s changelog, and review the record at least quarterly.
A cleaner alternative to flattening: move non-essential sending sources to subdomains. Each subdomain gets its own SPF record with its own 10-lookup budget.
Common Syntax Errors
Two SPF records on one domain. The most common mistake. Any domain with two TXT records that both start with v=spf1 returns permerror. Delete or merge them.
Exceeding 10 lookups. Usually caused by adding an ESP include: without auditing the total. Run an online SPF lookup counter before deploying changes.
+all or ?all in production. Either authorizes or ignores sources that should be rejected. Always use -all or ~all.
Missing include: for a sending service. If an ESP or CRM sends on your behalf and you do not include their SPF domain, their messages fail SPF and likely fail DMARC alignment too. Check with each sending service for the correct include: value. For example, ESPs like Coldletter provide an include: value in their documentation that you add to your record.
Trailing period after a domain. include:spf.protection.outlook.com. (with a trailing dot) is a syntax error. Drop the dot.
Equal sign instead of colon. include=domain is wrong. include:domain is correct.
Real-World Example Records
Google Workspace only
v=spf1 include:_spf.google.com -all
Per Google’s SPF setup documentation, _spf.google.com is the include value for Google Workspace. The -all is added here; Google’s own guide defaults to ~all.
Microsoft 365 only
v=spf1 include:spf.protection.outlook.com -all
Per Microsoft Defender for Office 365 documentation, this is the correct record for a custom domain using Microsoft 365 for all outbound mail.
Mixed environment: Microsoft 365, an ESP, and a dedicated IP
v=spf1 ip4:203.0.113.42 include:spf.protection.outlook.com include:mail.esp-domain.com ~all
This record uses three mechanisms: a dedicated IP for transactional mail, Microsoft 365 for employee email, and a third-party ESP for marketing campaigns. Lookup count: 2 (the two include: entries; ip4: is free).
Domain that sends no mail (parked domain)
v=spf1 -all
No mechanisms, just a hard fail. This tells every receiving server that no source is authorized to send email for this domain. Every parked or unused domain should have this record to prevent spoofing.
How SPF Feeds DMARC Alignment
SPF checks the MAIL FROM (also called the envelope sender or return-path) domain, not the From header your recipient sees. For SPF to contribute to DMARC alignment, the domain in the MAIL FROM must match the organizational domain in the visible From header. When it does, DMARC treats the SPF result as “aligned.” When it does not (common with forwarding or mailing lists), DMARC falls back to DKIM alignment.
Understanding this relationship is covered in detail in How to Set Up DMARC (Step-by-Step Guide). The short version: SPF alone does not protect the From address your recipients see. SPF plus DMARC does. For how authentication problems affect deliverability and sender reputation, SPF correctness is table stakes.
Frequently Asked Questions
What does -all vs ~all mean in an SPF record?
-all is a hard fail: any sending source not listed in your SPF record should be rejected. ~all is a soft fail: unlisted sources are accepted but typically flagged as spam. With DMARC in place, the practical difference narrows because DMARC applies your published rejection policy regardless. Without DMARC, -all is the only mechanism that enforces a hard rejection at the SPF layer. Start with ~all while auditing your senders; switch to -all once you are confident your record is complete.
How many DNS lookups can an SPF record have?
RFC 7208, Section 4.6.4 sets a hard limit of 10 DNS-querying mechanisms per SPF evaluation. Mechanisms that count toward the limit include include:, a, mx, exists, and redirect. The ip4:, ip6:, and all mechanisms do not count. The same section also sets a void-lookup limit of 2: DNS queries that return no records. Exceeding either limit produces a permerror, which is treated as a hard authentication failure by receiving servers.
Can I have two SPF records on one domain?
No. RFC 7208, Section 3.2 states that a domain “MUST NOT have multiple records that would cause an authorization check to select more than one record.” If two TXT records starting with v=spf1 exist on the same domain, SPF evaluation immediately returns permerror. Merge all your mechanisms into a single record and delete the duplicate.
What does include: do in an SPF record?
include:domain tells the evaluating server to fetch and run an SPF check against domain‘s own SPF record. If that check returns pass, the include: mechanism matches and the sending IP is authorized. This is how email service providers authorize senders: they publish their IP ranges under their own domain, and you reference it with one include:. Each include: costs one DNS lookup, and nested lookups inside the referenced record add to your total.
Why should I avoid the ptr mechanism?
RFC 7208, Section 5.5 states: “This mechanism SHOULD NOT be published.” The spec explains it is “slow, it is not as reliable as other mechanisms in cases of DNS errors, and it places a large burden on the .arpa name servers.” In practice, many receiving servers ignore ptr entirely. Use ip4:, ip6:, or include: instead to reliably authorize your sending sources.
What is SPF flattening and when should I use it?
SPF flattening replaces include: mechanisms with the resolved IP addresses, reducing DNS lookups to zero since ip4: and ip6: entries are free. It is a workaround for hitting the 10-lookup limit. The downside: cloud providers change their sending IPs without notice, so a flattened record can silently break when IP ranges rotate. Never flatten include:spf.protection.outlook.com or other major cloud providers with dynamic infrastructure. Better alternatives include moving non-critical senders to subdomains (each gets its own 10-lookup budget) or consolidating the number of sending services you authorize.
I’ve spent my career building software at scale with a soft spot for email: deliverability, lifecycle campaigns, and getting messages to actually land. I started Coldletter to fix what bugged me about transactional and marketing email tools. I’m based in Vancouver.
