Check your SPF record

Counts every DNS lookup and follows every include.

The lookups go from your browser to a public DNS-over-HTTPS resolver. Nothing is sent to Unitpost, and there's no sign-up.

SPF is a TXT record starting with v=spf1 that lists the servers allowed to send mail for a domain's Return-Path. While evaluating it, receivers allow at most 10 DNS lookups (include, a, mx, ptr, exists and redirect, nested includes counted) and 2 void lookups; past either limit SPF returns a permerror for every message. This checker follows every include, counts both, and flags syntax errors, duplicate records and risky all terms.

How SPF lookups are counted

Every term that makes the receiver query DNS counts toward a limit of 10 per evaluation (RFC 7208 §4.6.4), and the count includes every term inside nested includes. Terms that carry their answer in the record cost nothing.

TermLookupsWhy
include:1, plus every lookup in the included recordThe receiver fetches and evaluates the other record.
redirect=1, plus every lookup in the target recordThe other record replaces this one.
a, a:host1An address lookup for the host.
mx, mx:host1An MX lookup. More than 10 MX hosts behind one mx is a permerror on its own.
ptr1A reverse lookup of the sending IP. RFC 7208 says not to publish it.
exists:1An address lookup, usually on a macro.
ip4:, ip6:0The addresses are in the record.
all0Matches every sender; nothing to look up.
exp=0Fetched only after a fail, to explain it.

An include can cost several lookups when the record it names has includes of its own. The include tree in the result shows the count for each record, so you can see which one is expensive.

Void lookups

A void lookup is a DNS query that comes back with nothing: the name doesn't exist (NXDOMAIN), or it exists with no record of the type asked for. RFC 7208 has receivers stop after two, and a third makes the result a permerror.

They come from includes of domains that no longer exist (a provider you left, a typo) and from a: or mx: terms naming hosts that were deleted. The fix is to remove the term; it authorizes nothing.

Flattening an SPF record, and what to do instead

Flattening replaces includes with the ip4: and ip6: ranges they currently resolve to. It brings the lookup count down, but it freezes a list the provider can change at any time.

  • When a provider adds a range, mail from it fails SPF until you update your record.
  • The record grows. RFC 7208 asks for a record whose DNS answer fits in 512 bytes, and one TXT string holds at most 255 characters, so a long record has to be split into several strings.
  • Services that re-flatten automatically fix the staleness, but your SPF then depends on them publishing correctly.

Two changes usually bring the count down without flattening:

  • Remove includes for services you no longer send through, and any include that appears twice.
  • Give senders their own Return-Path subdomain. SPF is checked on the Return-Path domain, and each name has its own budget of 10 lookups. Providers that use a custom return-path subdomain publish SPF there, not in your apex record. Unitpost uses unitpost.<your domain>, so it adds nothing to your apex record.

~all or -all: softfail vs fail with DMARC

With DMARC enforced, the choice matters less than it seems: DMARC acts on SPF and DKIM together, and a message that fails SPF but passes aligned DKIM still passes DMARC.

EndingResult for unlisted serversUse it when
-allfailThe domain sends no mail (v=spf1 -all), or every sender is listed and you accept the forwarding risk below.
~allsoftfailThe default for a domain with DMARC at quarantine or reject.
?allneutralNever on a sending domain; it asserts nothing.
+all or allpassNever; it authorizes every server on the internet.

The catch with -all: some receivers reject on an SPF fail before they evaluate DKIM or DMARC (RFC 7489 §10.1). Forwarded mail always fails SPF, so -all can lose forwarded messages that DKIM would have carried. ~all with an enforced DMARC policy still stops spoofing.

Errors and how to fix them

No SPF record found

Also reported as: SPF record not found; No SPF record; spf=none

No TXT record at this name starts with v=spf1, so receivers get an SPF result of none and can't tell which servers may send for it.

  1. Check the right name. SPF is looked up on the Return-Path domain, which is often a provider's subdomain of yours; for Unitpost it is unitpost.<your domain>.
  2. If this name sends mail, publish one TXT record at it: v=spf1 include:<your provider's include> ~all, using the include each provider documents.
  3. If it sends no mail, publish v=spf1 -all so receivers can reject anything that claims to be from it.
  4. Wait for the TTL and check again. Other causes are in SPF record not found.

Multiple SPF records found

Also reported as: More than one SPF record; SPF permerror: multiple records; Found multiple SPF records

RFC 7208 allows one v=spf1 record per name. With two or more, receivers return a permerror and SPF fails for every message, not just mail from one of the providers.

  1. List what DNS serves: dig TXT example.com +short.
  2. Merge the terms into one record with a single v=spf1 at the start and a single all at the end: v=spf1 include:_spf.one.example include:_spf.two.example ~all.
  3. Delete the other records and check again.

SPF syntax error

Also reported as: Invalid SPF record; SPF permerror: unknown mechanism; SPF record is not valid

A term isn't valid SPF: an unknown mechanism, = where a mechanism takes :, a host name in ip4:, an invalid address, or a second redirect=. Receivers return a permerror for the whole record, so SPF fails for every message.

  1. Read the result line: it quotes the term and says what's wrong with it.
  2. Correct the term. include=_spf.example.com becomes include:_spf.example.com; ip4:mail.example.com becomes a:mail.example.com; ip4:203.0.113.300 needs a real address such as ip4:203.0.113.30.
  3. Separate terms with single spaces and check again after the TTL.

SPF permerror: too many DNS lookups

Also reported as: Too many included lookups; SPF record exceeds 10 DNS lookups; PermError: too many DNS lookups

Evaluating the record takes more than 10 DNS lookups once nested includes are counted. Receivers stop at 10 and return a permerror once a message's check reaches the 11th, so mail from senders listed late, or not listed at all, fails SPF. The same finding covers an mx term whose domain has more than 10 MX hosts, which RFC 7208 also makes a permerror.

  1. Find the expensive records in the include tree; each shows its own lookup count.
  2. Remove includes for services you no longer send through, and any include that appears twice.
  3. Replace a and mx terms with the ip4: and ip6: addresses they point at, when those are stable addresses you control.
  4. Move bulk or marketing senders to their own Return-Path subdomain, which has its own budget of 10.
  5. Flatten provider includes into IP ranges only as a last resort; see flattening.

SPF record is close to the 10-lookup limit

Also reported as: 9 of 10 SPF lookups used; SPF lookup count near limit

The record uses 9 or 10 of the 10 lookups receivers allow. It passes today, but one more include, or a provider adding a nested include to its own record, tips it into a permerror that fails every message.

  1. Remove includes for services you no longer use.
  2. Move a sender to its own Return-Path subdomain before you add the next provider.
  3. Check again after any provider tells you to change your SPF record.

SPF void lookup limit exceeded

Also reported as: Too many void DNS lookups; SPF permerror: void lookup limit; More than 2 void lookups

A void lookup returns nothing: the name doesn't exist, or has no record of the type asked for. Receivers allow 2 per evaluation and return a permerror on the third. One or two show as a warning, because each is a term that authorizes nothing.

  1. Find the terms that point nowhere: includes of domains that are gone, a: or mx: hosts that were deleted, or a typo in a name.
  2. Delete them, or correct the name.
  3. Check again.

A null DNS lookup was found for include

Also reported as: SPF void lookup; Include domain does not exist; SPF include returns NXDOMAIN

An include: or redirect= names a domain that doesn't exist. It counts as a void lookup, and because the included record can't be evaluated, receivers return a permerror for the whole record.

  1. Copy the include again from the provider's current setup instructions; a typo, or an include the provider retired, is the usual cause.
  2. If you no longer send through that provider, delete the term.
  3. Check again.

No valid SPF record for included domain

Also reported as: SPF include has no SPF record; Include target missing v=spf1; permerror: include domain has no SPF record

The domain named in include: or redirect= exists but publishes no v=spf1 record. RFC 7208 makes that a permerror once a message's check reaches it, so mail from senders not matched earlier in the record fails SPF.

  1. Check you used the provider's SPF include, not its website or another of its host names.
  2. If you no longer send through that provider, delete the term.
  3. If the include is one of yours, such as include:_spf.example.com, publish the SPF record at that name.

SPF include loop

Also reported as: SPF record includes itself; Recursive SPF include; SPF redirect loop

An include or redirect leads back to a domain already being evaluated, directly or through another record. Evaluation can never finish, and receivers end it with a permerror.

  1. Remove any include: of the domain's own name; it adds nothing.
  2. If two of your records include each other, keep the include in one direction only.
  3. Check again.

Found duplicate include on SPF path

Also reported as: Duplicate SPF include; Same include appears twice

The same include is reached more than once while evaluating the record: listed twice, or also included by another record you include. Each copy spends lookups and authorizes nothing new.

  1. Delete the extra copy from your own record.
  2. If a provider's record already includes it, remove yours.

SPF record uses +all

Also reported as: SPF allows all senders; +all mechanism; Permissive SPF record

+all, or all with no qualifier, matches every server on the internet, so anyone can send mail that passes SPF for this domain. So does an include whose record ends in +all, and an exists: with a fixed name that resolves: it matches whoever sends.

  1. List your real senders before the end of the record.
  2. Replace +all with ~all: v=spf1 include:<your provider's include> ~all.

SPF record ends in ?all (neutral)

Also reported as: Neutral SPF policy; SPF ?all

?all asserts nothing about servers the record doesn't list: their mail gets a neutral result, which receivers treat much like having no SPF.

  1. Make sure every service that sends as this domain is listed.
  2. Change the ending to ~all, and to -all later if you want; see softfail vs fail.

SPF record has no all mechanism

Also reported as: Missing all mechanism; SPF record does not end with all

Without an all or a redirect=, servers the record doesn't list get the default result, neutral, so the record never fails anything.

  1. End the record with ~all or -all: v=spf1 include:<your provider's include> ~all.

SPF record ends in ~all (softfail)

Also reported as: SPF softfail; ~all vs -all

~all marks mail from unlisted servers as suspicious without failing it outright, and leaves the decision to DMARC. That is a sound setting when DMARC is enforced, so it is reported as a note, not a problem.

  1. Keep ~all if your DMARC policy is quarantine or reject.
  2. Move to -all only once every legitimate sender is listed, and read softfail vs fail for the forwarding trade-off.

SPF record uses the ptr mechanism

Also reported as: ptr mechanism deprecated; Avoid ptr in SPF

ptr makes the receiver look up the sending IP's host name and then confirm it with a forward lookup. RFC 7208 says not to publish it because it is slow and unreliable, and some receivers skip it, so mail it was meant to cover can fail.

  1. Replace ptr with the ip4: and ip6: ranges, or the a: host, of the servers it was meant to cover.
  2. Check again.

SPF redirect ignored because of all

Also reported as: redirect= used with all; SPF redirect modifier ignored

RFC 7208 §6.1 has receivers ignore redirect= when the record contains an all mechanism anywhere, so the record you redirect to is never used.

  1. To hand the whole policy to another record, remove the all: v=spf1 redirect=_spf.example.com.
  2. To keep the policy here, remove the redirect= and list the senders in this record.

SPF mechanisms after all are ignored

Also reported as: Terms after all; include after ~all

Receivers read terms left to right and stop at all, which matches every sender. Anything after it is never evaluated, so senders listed there aren't authorized.

  1. Move the terms in front of all: v=spf1 include:_spf.one.example include:_spf.two.example ~all.
  2. Check again.

SPF record uses macros

Also reported as: SPF macro %{i}; exists:%{i} in SPF

A term contains a macro such as %{i} (the sending IP) or %{d} (the domain), which the receiver fills in for each message. It counts as one lookup, but a checker with no real message can't follow it, so this check doesn't call the record verified and the domain grade shows SPF as partly checked, at no cost. Macros are legitimate, and some large senders publish them with exists:.

  1. Nothing to fix if you added it on purpose or copied it from a provider's instructions.
  2. If you don't recognize it, find which service it belongs to before removing it; it may be what authorizes that service's mail.

SPF record has extra quotes or text before v=spf1

Also reported as: SPF record does not start with v=spf1; Quoted SPF record

A TXT record contains v=spf1 but doesn't start with it, usually because literal quote marks or a stray character were pasted into a DNS editor that adds its own quotes. Receivers only accept records that begin with v=spf1, so they ignore this one.

  1. Open the record in your DNS editor and remove the extra quote marks or leading characters.
  2. Check what DNS serves: dig TXT example.com +short should print the record inside one pair of quotes, which dig adds itself.
  3. Check again.

Frequently asked questions

How many DNS lookups does SPF allow?

Ten per evaluation, counting `include`, `a`, `mx`, `ptr`, `exists` and `redirect`, including those inside nested includes. `ip4`, `ip6` and `all` don't count. Past ten, receivers return a permerror and SPF fails.

Can a domain have two SPF records?

No. A name may have one `v=spf1` record, and two is a permerror that fails SPF for every message. Merge them into one record with several includes.

Should SPF end in ~all or -all?

Use `~all` on a sending domain with an enforced DMARC policy, and `-all` on domains that send no mail. Some receivers reject on an SPF fail before checking DKIM, which can cost you forwarded mail.

Does SPF check the From address?

No. SPF checks the envelope sender, the Return-Path domain. DMARC is what ties it to the From address, by requiring the two domains to align.

Do subdomains inherit the SPF record?

No. SPF is looked up on the exact name, so a subdomain that sends mail needs its own record. DMARC is the one that falls back to the parent domain.

What is an SPF permerror?

A permanent error: the record can't be evaluated as published, because of two records, a syntax error, too many lookups or a broken include. DMARC counts it as SPF not passing, for every message.

Go deeper: How to fix an SPF record that isn't found · Gmail, Yahoo and Microsoft bulk sender requirements · Sending domains in Unitpost

Free tools

Send from a domain that checks out

Unitpost sets up SPF, DKIM and DMARC with you and keeps checking them.