SPF Record Checker

Check any domain's SPF record the way receiving mail servers do: every include with its own DNS lookup cost, the running total against the limit of 10, void lookups, and the result for any sending IP, with the term that decided it.

Sources: the domain's TXT records and every include, through Google Public DNS (Cloudflare 1.1.1.1 behind it) · RFC 7208 check_host, run on our server · nothing is stored

What is an SPF record?

An SPF record is a TXT record that lists the servers allowed to send mail for a domain. It starts with v=spf1, then names senders term by term, and ends with what receivers should make of everyone else (RFC 7208, April 2014, still the current standard).

Receivers check it against the address of the server that connects and the domain in the envelope sender, the MAIL FROM or Return-Path, not the From: line a reader sees. Tying SPF to From: is DMARC's job.

A record covers only the exact name it sits at: SPF has no inheritance, so mail.example.com needs a record of its own. A domain that sends no mail should publish v=spf1 -all.

An SPF record, term by termgoogle.com · TXT · recorded October 24, 2026 · TTL 5 minutes
v=spf1Versioninclude:_spf.google.comMechanism · 1 lookup~allQualifier ~ · mechanism all

The include points at _spf.google.com · 8 ranges, no further lookups

ip4:74.125.0.0/16ip4:209.85.128.0/17ip6:2001:4860:4864::/56ip6:2404:6800:4864::/56ip6:2607:f8b0:4864::/56ip6:2800:3f0:4864::/56ip6:2a00:1450:4864::/56ip6:2c0f:fb50:4864::/56~all

VersionExactly v=spf1, first: a record that starts any other way isn't an SPF record.
Mechanismsinclude, a, mx, ip4, ip6, exists, ptr and all, tried left to right: the first match decides.
Qualifiers+ pass (the default), - fail, ~ softfail, ? neutral, written in front of a mechanism.
Modifiersredirect= hands the check to another record; exp= explains a fail.
google.com's whole record is three terms: a receiver fetches the include, matches the sending IP against its ranges, or reaches ~all. Recorded through Google Public DNS on October 24, 2026.

How to check an SPF record

Type a domain above and the checker reads its TXT records, picks the one that starts with v=spf1, then follows every include and redirect the way a receiving server would, counting each DNS lookup as it goes.

By hand, ask for the TXT records: dig TXT example.com +short or nslookup -type=TXT example.com. The answer holds every TXT record at the name; the SPF record is the one starting v=spf1, and a long one comes as several quoted strings that SPF joins with no space. Each include is one more query, and you would have to count them yourself.

To see whether a particular server may send, switch to Test an IP: it runs the same check for that address and shows the term that matched.

dig TXT example.com +short

Every TXT record at the name, as your resolver has it; the SPF record is the one starting v=spf1. Run it again for each include.

What dig shows, and what it doesn'tgithub.com · dig TXT · recorded October 24, 2026
terminal1 of 24 TXT records
$ dig TXT github.com @dns1.p08.nsone.net +norec +noall +answer
github.com.  300  IN  TXT  "v=spf1 ip4:192.30.252.0/22 include:spf.protection.outlook.com include:_netblocks.google.com include:_netblocks2.google.com include:mail.zendesk.com include:_spf.salesforce.com include:servers.mcsv.net include:mktomail.com include:sendgrid.net ip4:62.253.2" "27.114 ip4:166.78.69.169 ip4:166.78.69.170 ip4:166.78.71.131 ~all"

# then each include, one query at a time:
$ dig +short TXT _spf.salesforce.com
"v=spf1 exists:%{i}._spf.mta.salesforce.com -all"
  1. One record starts with v=spf1. The 23 other TXT records beside it (verification tokens and the like) don't matter to SPF, except for the answer's size.
  2. 2 strings, joined with no space. The 255-byte split falls inside ip4:62.253.227.114: receivers join the strings back together, so it's fine.
  3. TTL 5 minutes. How long resolvers may keep this copy after a change. The zone's own server says 5 minutes; a resolver shows what is left of its copy.
  4. Every include is its own query. Following github.com's record takes 10 lookups, nested ones included. dig shows one name at a time: the count to 10 is yours to add up.
Windows
$ nslookup -type=TXT github.com 8.8.8.8
PowerShell
# every TXT record at the name, from Google Public DNS
PS> Resolve-DnsName -Name github.com -Type TXT -Server 8.8.8.8
github.com's TXT answer from its own nameserver: the SPF record arrives as two strings, joined with no space, among the domain's other TXT records.

Reading the result: the include tree

The verdict says what receivers get for a sender the record doesn't list: valid with its ending (softfail for ~all, fail for -all), or a permerror with its cause, or "couldn't check" when DNS didn't answer.

The navy column is the lookup budget: one cell per DNS lookup, coloured by the include that spends it, the running total against 10, and void lookups as rings. Past the 10th, the cells turn red and the branch where receivers stop is marked.

The Tree view lists the readings, failures first, each with its RFC section and the fix, then every lookup in the order a receiver asks.

SPF record check of github.com: 10 of 10 DNS lookups, one void, each include's cost in the lookup budgetSPF record check of github.com: 10 of 10 DNS lookups, one void, each include's cost in the lookup budgetThe verdictWhat receivers get for a sender the record doesn't list, and the ending that decides it.The record, term by termEach term coloured by kind; an include carries its owner's mark and its own lookup cost.The readingsOne record, valid syntax, lookups, voids, the ending, ptr, DMARC: failures first, each with its fix.The lookup budgetEvery DNS lookup as a cell, coloured by the include that spends it, against the limit of 10.Each include's costA branch per include: its own cells, what it nests and the running total.The void lookupsRings for queries that came back empty; the third ends the check.
An SPF check of github.com: the verdict, the record split into its terms, the readings and the lookup budget with each include's own cost. Select a number to see what each part shows.

The 10-lookup limit and "too many DNS lookups" (permerror)

Receivers count every term that needs a DNS query, include, a, mx, ptr, exists and redirect, through every nested include, and stop at 10. The 11th gives permerror (RFC 7208 §4.6.4). ip4, ip6, all and exp cost nothing.

It's a permerror, not a fail: what a receiver does with it is its own choice, but under DMARC a permerror never counts as a pass. And because the check stops at the first match, a record over the limit still passes for senders listed early and breaks only for those listed late.

The cost hides inside includes: include:_netblocks.mimecast.com spends 9 by itself, Yandex 360's 8, Freshdesk's 7, Mailgun's 5. Google's and Microsoft 365's includes cost 1 each. To get under 10, drop senders you no longer use and move a bulk sender to a subdomain with its own record.

Who spends the 10 lookupsCosts measured October 24, 2026 · a made-up record of real includes

v=spf1 include:spf.protection.outlook.com include:_netblocks.mimecast.com include:mailgun.org include:sendgrid.net -all

17 lookups needed
Listed within the first 10 lookups: passMicrosoft 3651Mimecast2–10
Listed after lookup 10: permerrorMailgun11–15Twilio SendGrid16–17

Everyone else gets permerror too, not the record's -all fail: receivers stop before they reach it.

What one include costsEach provider's own documented include · measured October 24, 2026
  • _netblocks.mimecast.comMimecast9 lookups
  • _spf.yandex.netYandex 3608 lookups
  • email.freshdesk.comFreshdesk7 lookups
  • mailgun.orgMailgun5 lookups
  • icloud.comiCloud+5 lookups
  • spf.titan.emailTitan Email4 lookups
  • secureserver.netGoDaddy3 lookups
  • _spf.google.comGoogle Workspace1 lookup
  • spf.protection.outlook.comMicrosoft 3651 lookup
Lookups counted the way RFC 7208 §4.6.4 does: the include itself plus everything inside it. Live counts on this page always come from the walk, not this table.

What are void lookups?

A void lookup is a query that comes back with no records: the name doesn't exist (NXDOMAIN) or exists without the record asked for. Receivers should allow two; the third is permerror (RFC 7208 §4.6.4).

A term pointing at a host that's gone, a:old.example.com, isn't an error by itself: it just doesn't match and counts one void. So does exists:%{i}._spf.mta.salesforce.com for every sender Salesforce doesn't list, which is why the order of includes matters.

A server failure or a timeout is not a void: it's a temperror, and receivers usually try again later.

Four answers, three outcomesRFC 7208 §4.6.4

salesforce.com's own record for 192.0.2.1, recorded October 24, 2026

v=spf1 include:_spf.google.com include:_spf.salesforce.com exists:%{i}._spf.corp.salesforce.com ~all

Void lookups2 / 2a third: permerror
  1. exists:%{i}._spf.mta.salesforce.com192.0.2.1._spf.mta.salesforce.com → NXDOMAIN · lookup 3 · in _spf.salesforce.com
  2. exists:%{i}._spf.corp.salesforce.com192.0.2.1._spf.corp.salesforce.com → NXDOMAIN · lookup 4 · in salesforce.com

Result: softfail, with both allowed voids spent

The name exists, the record doesn'ta:mail.example.com for a name that is still there but no longer has an address: NOERROR with no answer. No match, one void, and the check goes on.NODATAvoid
The name doesn't existexists:%{i}._spf.mta.salesforce.com builds a name from each sending IP; for an address Salesforce doesn't list, the answer is NXDOMAIN: one void every time.NXDOMAINvoid
The third voidmx:shop.example.com, a name that is gone, after 2 voids already: receivers stop here.NXDOMAINpermerror
No answer at allSERVFAIL or a timeout for include:_spf.vendor.example: the DNS failed, so nothing counts as void.SERVFAILtemperror, not a void

blastmail's include include:spf.bmv.jp starts with two such exists terms: for an address it doesn't list, it spends both allowed voids by itself.

Only an empty answer counts as void. A failure is a temperror, and the check is tried again later.

SPF flattening: when it helps and when it breaks

Flattening replaces an include with the addresses it lists, so the record costs fewer lookups. It works for ranges that rarely change, and it backfires when a provider adds a server you never copied: mail from it then fails SPF.

Microsoft says not to flatten include:spf.protection.outlook.com, and Google's and Microsoft's includes already cost one lookup each. Moving a bulk sender to a subdomain such as news.example.com gives it a record, and 10 lookups, of its own.

One include, flattenedmailgun.org · recorded October 24, 2026

Before

include:mailgun.org

Lookups
5
Records fetched
5
Bytes in your record
19
Mailgun changes a range
picked up at once

After

ip4:159.135.224.0/20 ip4:69.72.32.0/20 ip4:204.220.90.0/23 ip4:204.220.92.0/22 +18 more

Lookups
0
Bytes in your record
450
TXT strings needed
2
Mailgun changes a range
your copy is stale

The stale-copy risk. You copy 22 ranges today; when Mailgun adds one, mail from it fails SPF until someone notices and updates the record.

  1. copied
  2. Mailgun adds a range
  3. mail from it fails SPF
Flatten only ranges that rarely change, such as your own servers' addresses.
Don't flatten Microsoft 365: Microsoft says its sending addresses change often, so keep the include.
Prefer a subdomain per bulk sender: each gets a record, and 10 lookups, of its own.
Flattening trades lookups for maintenance: the copy is right only until the provider changes its ranges.

~all vs -all (and DMARC)

The last term says what happens to every sender not listed. -all is fail, the strongest statement; ~all is softfail, "probably not"; ?all is neutral, which receivers treat like no SPF at all; +all lets everyone pass and should never be published.

Between ~all and -all there is no wrong answer. Google documents ~all for Google Workspace and Microsoft -all for Microsoft 365. With DMARC at enforcement, ~all lets DMARC decide on DKIM: some receivers reject on -all before DMARC sees a passing DKIM signature, so forwarded mail can bounce (RFC 9989 §7.1).

The four endingsRFC 7208 §2.6 · RFC 9989 §7.1
~all
softfail“Probably not authorised”: receivers should not reject on it alone.
Documented byGoogle WorkspaceMailguniCloud+
-all
failThe strongest statement: a receiver may reject the message in SMTP.
Documented byMicrosoft 365ZendeskGoDaddy
?all
neutralNo statement at all: receivers treat it exactly like no SPF.
Documented byFastmailThe includes of IONOS and OVHcloud end in ?all too: harmless there, since only a pass inside an include matches.
+all
pass for everyoneAny server on the Internet passes as the domain.
Never publish it

-all with DMARC at p=reject

  1. SPF fail
  2. rejected in SMTP
  3. DKIM never checked

Some receivers act on SPF first, so a forwarded message with a valid DKIM signature can be refused before DMARC runs, and the loss never shows in DMARC reports.

~all with DMARC at p=reject

  1. SPF softfail
  2. DMARC checks DKIM
  3. aligned DKIM: pass

DMARC decides. Without a DKIM signature, though, Microsoft says DMARC is effectively ignored for ~all failures, and recommends -all for that reason.

Both ~all and -all are valid choices; ?all says nothing, and +all lets anyone pass.

Common SPF errors and how to fix them

Most broken records come from three slips: a second SPF record added beside the first instead of merging them, an include for a vendor that has since gone, and a value copied from documentation with a typo in it.

Vendor docs print some of these themselves: Mimecast writes –all with an en dash, Postmark include: spf.mtasv.net with a space, Twilio SendGrid lists an aaaa mechanism that doesn't exist. Any syntax error makes the whole record permerror. A trailing dot after a domain, on the other hand, is valid.

Eight records, eight resultsMade-up examples of real mistakes

Two SPF recordspermerror

v=spf1 include:_spf.google.com ~allv=spf1 include:sendgrid.net ~all

Fix: merge them into one: one v=spf1, every include once, one ending.

More than 10 lookupspermerror

v=spf1 include:_netblocks.mimecast.com include:mailgun.org -all

14 lookups

Fix: drop senders you no longer use, or give a bulk sender a subdomain of its own.

A third void lookuppermerror

v=spf1 a:old.example.com mx:gone.example.net exists:%{i}.spf.example.org -all

3 voids

Fix: delete terms whose names are gone, and put exists includes last.

An include with no SPF record behind itpermerror

v=spf1 include:spf.retired-vendor.example -all

Fix: correct the name, or remove the vendor you left.

An en dash in –allpermerror

v=spf1 include:_netblocks.mimecast.com –all

Printed this way in Mimecast's own documentation

Fix: a plain hyphen: -all.

A space after include:permerror

v=spf1 a mx include: spf.mtasv.net ~all

Printed this way in Postmark's own documentation

Fix: include:spf.mtasv.net

An aaaa mechanismpermerror

v=spf1 aaaa:example.com ~all

Printed this way in Twilio SendGrid's own documentation

Fix: a:example.com; a already matches the AAAA records of IPv6 senders.

A trailing dotvalid

v=spf1 include:spf.protection.outlook.com. -all

Valid: RFC 7208 §7.3 says not to publish it, but receivers must accept it.

Zendesk's help also says “redirect: syntax is supported”: the modifier is written redirect= (RFC 7208 §6.1).

Each record is a made-up example; the mistakes are the ones receivers see most.

What Gmail, Yahoo and Microsoft require

Since February 2024 Gmail and Yahoo require every sender to set up SPF or DKIM. Bulk senders need both, a DMARC record of at least p=none, and a From: domain aligned with one of them. Outlook.com asks high-volume senders for SPF and DKIM both, and has rejected those without them since May 5, 2025.

Gmail's threshold is more than 5,000 messages a day to personal accounts; Yahoo publishes none. SPF alone never makes mail "aligned": the MAIL FROM domain has to be yours, not the provider's bounce domain.

The three mailbox providers' rulesRead on each provider's own pages, October 24, 2026
GmailYahooOutlook.com
Bulk thresholdGmailmore than 5,000 messages a day to personal Gmail accounts, counted per primary domainYahoonone published: “We will not specify a volume threshold”Outlook.commore than 5,000 a day (Microsoft's blog); 5,000 or more from one From: domain (its support article)
All sendersGmailSPF or DKIM, plus forward and reverse DNS, TLS and a spam rate under 0.3%YahooSPF or DKIM, plus forward and reverse DNS and a spam rate under 0.3%Outlook.comno hard rule
Bulk sendersGmailSPF and DKIM, DMARC (p=none is enough), From: aligned with SPF or DKIM, one-click unsubscribe for marketing mailYahooSPF and DKIM, DMARC at least p=none and passing, From: aligned with SPF or DKIM, one-click unsubscribeOutlook.comSPF must pass and DKIM must pass, DMARC at least p=none, aligned with SPF or DKIM
In force sinceGmailFebruary 1, 2024; rejections ramped up from November 2025YahooFebruary 2024Outlook.comMay 5, 2025 (rejection)
SPF failure shows asGmail4.7.275.7.27Yahoonot documented for SPFOutlook.com550 5.7.515

Japan: NTT docomo's strictest setting, 「全て拒否する」, refuses mail whose sending IP the domain's SPF record doesn't authorise, and mail from domains that publish no SPF record at all. docomo.ne.jp

Sourcessupport.google.comsenders.yahooinc.comtechcommunity.microsoft.com

Each provider's rules as its own pages state them on October 24, 2026.

SPF lookup vs dig and nslookup

dig and nslookup show the TXT records as your resolver has them: the SPF record and whatever else sits at the name. They don't follow includes, count lookups or voids, or say what a receiver would decide.

An SPF lookup like this one walks the whole record, so the numbers match what a receiving server computes. Use dig for one name, this checker for the verdict.

Three ways to look at SPFWhat each one shows
This checkerdig TXTnslookup -type=TXT
Shows the TXT recordsThis checkerYessplit into termsdig TXTYesnslookup -type=TXTYes
Joins a long record's stringsThis checkerYesas receivers dodig TXTNoshows each string apartnslookup -type=TXTNoshows each string apart
Follows every includeThis checkerYesthe whole treedig TXTNoone name per querynslookup -type=TXTNoone name per query
Counts lookups to 10This checkerYesa running total, per includedig TXTNoby handnslookup -type=TXTNoby hand
Counts void lookupsThis checkerYeswith the namesdig TXTNonslookup -type=TXTNo
Says two records = permerrorThis checkerYesdig TXTNoprints bothnslookup -type=TXTNoprints both
Tests a sending IPThis checkerYescheck_host and the matching termdig TXTNonslookup -type=TXTNo
Asks your own resolverThis checkerNoGoogle Public DNS, from our serverdig TXTYesnslookup -type=TXTYes
The same question asked three ways: dig and nslookup show what your own resolver has, the checker what a receiver computes.

SPF checker: frequently asked questions

What is an SPF record check?

An SPF record check reads a domain's SPF record, the TXT record starting with v=spf1, and walks it the way a receiving mail server does: every include and redirect followed, each DNS lookup counted against the limit of 10 and each empty answer against the limit of 2. It ends with what receivers get for an unlisted sender, or the permerror and its cause.

How do I test if an IP address is allowed to send email for my domain?

Choose Test an IP, enter the domain and the sending server's address, IPv4 or IPv6, and optionally the MAIL FROM address and HELO name. The checker runs check_host for that address and shows pass, fail, softfail, neutral or permerror, the exact term that matched, its qualifier and the chain of includes that led to it.

What does "SPF too many DNS lookups" mean?

It means checking the record takes more than 10 DNS lookups, the limit in RFC 7208. Each include, a, mx, ptr, exists and redirect costs one, including those nested inside includes. Receivers stop at the 11th with permerror, so senders listed after that point stop passing SPF. Remove unused includes, or move a sender to a subdomain with its own record.

What is an SPF permerror?

A permerror is SPF's "this record can't be evaluated" result. It comes from more than 10 DNS lookups, more than 2 void lookups, two SPF records at one name, an include of a name without an SPF record, or a syntax error. It isn't a fail: receivers decide what to do with it, but under DMARC it never counts as a pass.

What is a void lookup in SPF?

A void lookup is a DNS query made for an SPF term that comes back empty: the name doesn't exist (NXDOMAIN) or has no record of the type asked. RFC 7208 says receivers should allow two; the third is permerror. A server failure or timeout is not a void lookup but a temperror, which receivers usually retry later.

Should my SPF record end with ~all or -all?

Both are valid. -all (fail) is the strongest statement and Microsoft documents it for Microsoft 365; ~all (softfail) is what Google documents for Google Workspace. With DKIM signing and DMARC at quarantine or reject, ~all is often the safer ending, because some receivers reject on -all before DMARC can accept a passing DKIM signature.

Can a domain have two SPF records?

No. If a name has two TXT records starting with v=spf1, receivers can't pick one and the check ends in permerror before any lookup, for every sender (RFC 7208 §4.5). This usually happens when a new provider's record is added beside the old one. Merge them into one record with one v=spf1, every include once and a single ending.

What is SPF flattening, and should I use it?

Flattening replaces includes with the IP ranges they list, so the record needs fewer DNS lookups. It helps for ranges that rarely change, but a flattened copy goes stale as soon as the provider adds servers, and mail from them then fails SPF. Prefer removing unused includes or giving a bulk sender its own subdomain; never flatten Microsoft 365's include.

Does a subdomain need its own SPF record?

Yes. SPF has no inheritance: a record covers only the exact name it is published at, so mail.example.com or news.example.com needs its own record if it appears in MAIL FROM or HELO. This differs from DMARC, which falls back to the organisational domain. A subdomain that sends no mail can publish v=spf1 -all.

Why does SPF pass here but fail at Gmail?

Usually because Gmail checked a different domain or address. SPF is evaluated for the MAIL FROM (Return-Path) domain and the connecting server's IP, which can be your provider's bounce domain, not yours. Forwarding also breaks SPF, since the forwarder's IP isn't in your record. Check a real message's Authentication-Results header and test that IP and domain here.

What is the difference between SPF, DKIM and DMARC?

SPF lists the servers allowed to send for a domain and is checked against the envelope sender. DKIM signs each message with a key published in DNS, so the signature survives forwarding. DMARC ties them to the From: address a reader sees: a message passes DMARC when SPF or DKIM passes for a domain aligned with From:, and the policy says what to do otherwise.

How long does an SPF record change take to work?

As long as resolvers keep the old record: up to its TTL, which the domain sets (300 seconds for google.com and github.com, an hour for microsoft.com when we checked). Receivers ask their resolver, so a change shows once each cached copy expires. This checker shows the TTL as the domain's own nameserver serves it; check again after that time.

Missing something?Suggest a feature or a new tool