Email deliverability

SPF record checker and generator

Count an SPF record’s DNS lookups the way receivers do, see whether a sending IP passes, and build a record that stays under the limit.

By using these tools you accept the Terms and the Privacy page.

The domain in the envelope sender (Return-Path), or any domain you want to audit.

Runs RFC 7208 check_host() for this address.

Only matters for records that use macros.

How the SPF checker reads a record

The SPF checker reads a domain’s SPF record, follows every include and redirect behind it, and shows whether receivers can evaluate it. It is for anyone adding a sending service, taking over a domain, or tracing SPF failures in DMARC reports.

What the results show

  • The record, with terms that are broken or never match marked, and terms receivers ignore dimmed.
  • The lookup tree: every include and redirect, the record it found and what each branch costs, with missing records, loops and void terms flagged.
  • Findings, worst first, most with a suggested fix.
  • Terms explained: each term’s result, lookup cost and purpose.

Problems it flags besides the limits

  • More than one v=spf1 record at a name, an include that finds no SPF record, or includes that loop back on each other. Each is a permanent error.
  • An include whose own record ends in +all, which lets every server pass for your domain too.
  • Terms after all, which never run, and a redirect= that is ignored because the record also has all.
  • The same domain included more than once, which costs its lookups again each time.
  • IPv4 ranges wider than /16 or IPv6 ranges wider than /32, which let anyone on those networks pass, and internal, loopback or documentation addresses, which do nothing.
  • A record over 450 characters, which may not fit in one DNS packet.
  • With no SPF record, a TXT record that only looks like one, such as one with quotes pasted in. Receivers read only a record that starts with exactly v=spf1 and a space.

How the grade is worked out

A permanent error, such as more than 10 lookups or more than two void lookups, gives an F, and so does +all, in the record or in one it includes. Any other record starts at 100 and loses 30 for ending in ?all or without all, 10 for using all 10 lookups (5 for 9), 10 for each void lookup, 10 for ptr and 10 for very broad ranges in your own record. Terms that never run, a repeated include you can drop from your own record, a record over 450 characters and an include or redirect that couldn’t be looked up cost 5 each. A+ needs 95, A 85, B 70, C 55 and D 40.

Testing a sending IP, and how long a check takes

With an address in Sending IP to test, the page runs RFC 7208 check_host() alongside the audit, reusing the same lookups: mechanisms in order, includes and redirects followed, macros expanded with the envelope sender you give (or postmaster at the domain), and the 10-lookup, two-void and 10-MX-host limits enforced. An IPv4-mapped IPv6 address is tested as IPv4. The answer shows the evaluation path, with includes that didn’t decide it folded away.

Lookups go from your browser to Cloudflare’s DNS-over-HTTPS resolver, with Google’s as a fallback, up to eight at a time, and each include is followed the moment its record arrives. After three seconds the loading line counts seconds and queries. A lookup still unanswered after 20 seconds is marked “couldn’t check”, never read as a missing record. For a record padded with thousands of terms, at most 60 records and 30 a or mx targets are looked up; every term is still counted.

Building a record

In the Build a record tab, ten common services fill in the include their own documentation gives, with a note where a provider adds a condition. Add ip4, ip6, a, mx or exists terms in the order receivers should check them and pick the ending; the page counts nested lookups as you go. The record comes as one TXT value and in zone-file form, split into strings of up to 255 characters. Check it now shows what your domain publishes today. Once it’s live, check your policy in the DMARC checker, and try the SPF flattener only if nothing else gets you under 10 lookups.

Updated · Maintained by Liviu Tanase

Questions

What does the 10-lookup limit count?

Every include, a, mx, ptr and exists mechanism and every redirect costs one DNS lookup, and the lookups inside included records count too, because the limit applies to the whole evaluation (RFC 7208 §4.6.4). ip4, ip6 and all cost nothing. When evaluating the record would need more than 10, receivers stop with permerror, which DMARC treats as an SPF fail. Evaluation stops at the first matching term, so a sender listed before the limit is reached can still pass; senders further down fail. An mx term also may not resolve more than 10 mail hosts.

What is a void lookup?

A lookup that finds nothing: the name doesn’t exist, or it exists but has no record of the type asked for. A term like a:old-server.example.com pointing at a deleted host is the usual cause. Receivers should return permerror when an evaluation hits more than two void lookups, so this check counts them too.

Should my record end in -all or ~all?

Both are fine once every sender is listed. -all asks receivers to reject unlisted senders, and some do so before DMARC is checked, which can bounce forwarded mail that would have passed on DKIM (RFC 9989 §7.1). ~all with DMARC at quarantine or reject leaves the decision to DMARC. Avoid ?all, which says nothing, and never use +all, which lets anyone pass.

How does the sending IP test work?

It runs the RFC 7208 check_host() evaluation in your browser: mechanisms left to right, includes and redirects followed, macros expanded, the lookup and void limits enforced, and the result shown with the path that produced it. Two parts are not evaluated: ptr is counted but treated as no match, and the %{p} macro expands to “unknown”. The result says so when that could matter.

How do I fix “too many DNS lookups” (SPF permerror)?

Check the domain here and open the lookup tree: it shows what each include costs, nested lookups included. Then remove includes for services you no longer use, replace a and mx terms for your own servers with ip4 or ip6 ranges, which cost nothing, drop any domain that is included twice, and move a sending service such as a newsletter tool to a subdomain with its own SPF record. Above 8 lookups the checker may also show a flattened version; treat it, and the SPF flattener, as a last resort, because a flattened record goes stale.

Can a domain have two SPF records?

No. RFC 7208 allows exactly one TXT record starting with v=spf1 at a name. With two, receivers return permerror and SPF fails for every message, so the checker grades it F. Merge every include and range into one record and delete the other; the Build a record tab assembles a single record from the services you pick. Other TXT records at the same name, such as site-verification tokens, don’t matter.

Why does SPF pass but DMARC still fail?

SPF checks the envelope sender (Return-Path) domain, not the From address people see. DMARC counts an SPF pass only when that domain matches the From domain. Many sending services use their own bounce domain, so SPF passes for them but not for you. Set up a custom return-path with the provider, or make sure it signs with DKIM for your domain. Check the policy with the DMARC checker and the signature with the DKIM checker.

Should I flatten my SPF record?

Only as a last resort. Flattening replaces an include with the IP addresses it lists today, which saves lookups, but providers change their addresses without notice, and a stale copy silently fails their mail. If you flatten, re-check it often or use a service that updates it. Dropping unused includes or moving a sender to a subdomain is safer.

Is anything sent to zerobounce.ai?

No. The lookups run from your browser to Cloudflare and Google DNS-over-HTTPS, and the record builder works entirely on the page. What you check isn’t sent to zerobounce.ai, unless it is in a link you open (a shared result, for example), which appears in the request logs described on the privacy page. The envelope sender you type is never put in the page’s link.