SPF Checker
How the checker expands an SPF record through every include and redirect, counts the DNS lookups receivers allow, grades every term, and builds a flattening plan that replaces only the includes it must.
The tool page is tamperbox.dev/spf-checker. It opens on a live check of tamperbox.dev; replace that domain to inspect your own. This page is the long version: how the record is expanded, every rule applied to it, what each finding means for mail delivery, and how to fix and verify each one.
What the tool answers
Section titled “What the tool answers”SPF lets a domain publish which servers may send mail for it. Receivers read the record, follow its includes, and allow at most 10 DNS lookups while doing so. The tool answers:
- How many lookups does this record cost? Every include, redirect, a, mx, ptr and exists counts, through every nested include.
- Is the record valid? Every term is parsed against RFC 7208; one unknown term makes the whole record a permanent error.
- Which includes could be flattened, and what would the record look like? Only includes that publish plain address ranges are candidates.
- What else is wrong with it? A missing or permissive all, ptr, duplicates, void lookups, a record over 255 characters.
It does not evaluate a sender address. A real receiver stops at the first matching mechanism, so mail matched early can still pass a record that is over the limit.
Inputs
Section titled “Inputs”A domain
Section titled “A domain”example.com. When you stop typing, or on Enter, the server reads the TXT records at the domain and the browser keeps the ones that start with v=spf1. Each include and redirect target is looked up the same way, ten levels deep at most and 100 names in total.
A pasted web address or email address, such as https://example.com/contact or postmaster@example.com, is cleaned to the domain it holds, and the page says what it kept. Text with no usable domain stays as it was, and the field explains what is wrong. A domain with non-ASCII labels is converted to A-labels before it is looked up.
An SPF record
Section titled “An SPF record”Paste the record itself, with or without the quotes DNS tools print: v=spf1 include:_spf.example.net ~all. It is parsed in the browser as the root of the tree, shown as “Your record”. Its includes are still looked up through the server, so the count covers what a receiver would read. Two records on two lines are treated as two published records, which is itself a finding.
Drop a .txt file anywhere on the page, or use Open file. It is read in the browser.
Reading the result
Section titled “Reading the result”Verdict and chips
Section titled “Verdict and chips”The verdict names the outcome: the budget exceeded, the record broken, something to check, or the record checked. The line under it carries the count and, when the record is over the limit, how far the flattening plan brings it. The chips show the count against the limit, the all mechanism, whether every include was looked up, and the number of findings; each one is a button that scrolls to the section that explains it.
Findings
Section titled “Findings”Everything found, worst first. Two or more passes fold into one line. A budget finding carries a “See the plan” button. The finding reference lists them all.
Take it further
Section titled “Take it further”Links that carry a value derived from this result into another tool: the biggest include on its own (this page again, with that domain), the DMARC policy that uses the record, who receives mail for the domain, and the network behind the first address the record lists. Each link opens the destination with the value already in its box; nothing is put in the URL.
History and Heal it
Section titled “History and Heal it”History logs every Break it and Heal it change with what it replaced; Revert undoes the newest, Reset input returns to what you entered. Heal it offers the repairs the tool found: the flattened record, merging two records into one, changing +all to ~all, adding ~all, removing ptr, removing an invalid address mechanism, correcting a misspelt include, removing duplicates.
Lookup budget
Section titled “Lookup budget”The count against the limit, and one slot per lookup in the order a receiver makes them, each slot named by the term and the record it came from. The tenth slot is marked; anything beyond it is red. The note under the slots says how many lookups are left, or that an evaluation reaching the eleventh stops there.
Include tree
Section titled “Include tree”The record and every include and redirect under it, each with the record it publishes and how many lookups it costs with everything beneath it. A branch that could not be evaluated shows the reason in red.
Flattening plan
Section titled “Flattening plan”Shown only when the record is over the limit. Each top-level include is marked Flatten or Keep with the reason, the flattened record is shown split into 255-character strings with a copy button, and the note says how many characters and strings it has.
The record, term by term
Section titled “The record, term by term”Every term with its meaning and whether it costs a lookup; an unknown term is marked.
Method
Section titled “Method”Every rule below names the section of RFC 7208 it comes from. Rules that decide a FAIL quote the normative sentence behind them. Rules marked “Tamperbox” are our own recommendations, not RFC requirements.
Record selection
Section titled “Record selection”The server asks public DNS for the TXT records at the name, as section 4.4 prescribes (“querying for type TXT only”), and the browser keeps the answers that start with v=spf1 followed by whitespace or the end of the record. Section 4.5 decides the outcome: “If the resultant record set includes no records, check_host() produces the “none” result. If the resultant record set includes more than one record, check_host() produces the “permerror” result.”
- No TXT record at the name (NXDOMAIN or an empty answer) is FAIL, “No record at domain.”
- TXT records, none of them SPF, is FAIL, “No SPF record at this name.”
- Two or more records is FAIL, “N SPF records: receivers return permerror.” The first record is still parsed and expanded, so the rest of the result describes it.
- A record published as several quoted strings is joined first, as section 3.3 requires: “the record MUST be treated as if those strings are concatenated together without adding spaces.”
Term syntax
Section titled “Term syntax”The record is split on whitespace. Each term after v=spf1 is a mechanism from section 4.6.1 with an optional +, -, ~ or ? qualifier, or a name=value modifier. Names are compared without case, because “mechanism and modifier names are case-insensitive” (section 4.6.1). Every syntax error is FAIL, because section 4.6 says: “if there are any syntax errors anywhere in the record, check_host() returns immediately with the result “permerror”“.
| Rule | Finding | RFC section |
|---|---|---|
| A mechanism name outside the eight in 4.6.1 | Unknown term X | 4.6.1 |
ip4 value that is not four octets from 0 to 255 without leading zeros |
Invalid IPv4 address in X | 5.6 |
ip6 value that does not parse as an IPv6 address |
Invalid IPv6 address in X | 5.6 |
| IPv4 prefix over /32 (also reported as an invalid prefix) | Prefix too long in X | 5.6 |
IPv6 prefix over /128, the dual /n//n form on ip4 or ip6, a prefix on any mechanism other than ip4, ip6, a and mx |
Invalid prefix in X | 5.6 |
include or exists without a domain |
include needs a domain, exists needs a domain | 5.2, 5.7 |
redirect= or exp= with an empty value |
redirect needs a domain, exp needs a domain | 6.1, 6.2 |
A value attached to all |
Invalid all mechanism X | 5.1 |
| The same modifier name twice | Duplicate X modifier | 6 |
Modifier rules
Section titled “Modifier rules”The duplicate rule follows section 6: “These two modifiers MUST NOT appear in a record more than once each. If they do, then check_host() exits with a result of “permerror”.” The tool applies it only to redirect and exp; repeated unknown modifiers are ignored for validity.
An unknown modifier is CHECK, “Unknown modifier X”. Section 6 says “Unrecognized modifiers MUST be ignored no matter where, or how often, they appear in a record”, so it never breaks the record; the tool reports it because a misspelt redirect is silently ignored the same way (Tamperbox).
Which terms are evaluated
Section titled “Which terms are evaluated”Section 4.6.2: “Each mechanism is considered in turn from left to right.” The tool walks the terms in that order and stops at the first all, following section 5.1: “Mechanisms listed after “all” MUST be ignored.” Terms after it are not counted. redirect is followed only when the record has no all, after every mechanism, because section 6.1 says: “Any “redirect” modifier MUST be ignored if there is an “all” mechanism anywhere in the record.” The lookup budget numbers the slots in this order, descending into each include as it is reached.
The lookup budget
Section titled “The lookup budget”Section 4.6.4 lists the terms that cause DNS queries: include, a, mx, ptr, exists and redirect. Each costs one; ip4, ip6, all and exp cost nothing. The rule: “SPF implementations MUST limit the total number of those terms to 10 during SPF evaluation, to avoid unreasonable load on the DNS. If this limit is exceeded, the implementation MUST return “permerror”.”
The count is one total for the whole tree, because section 4.1 says the limit “must be tracked as a single global limit for all evaluations, not just for a single instance of a recursive evaluation.” An include reached twice through different branches is counted twice.
| Total | Severity | Title | RFC section |
|---|---|---|---|
| 11 or more | FAIL | N of 10 DNS lookups | 4.6.4 |
| 9 or 10 | CHECK | N of 10 DNS lookups | none (Tamperbox early warning) |
| 8 or fewer | PASS | N of 10 DNS lookups | 4.6.4 |
The total is what a receiver spends on a sender that matches nothing, since then every term is evaluated. For a sender matched earlier, “processing ends” at the match (section 4.6.2) and fewer lookups are made.
Void lookups
Section titled “Void lookups”Section 4.6.4 also limits lookups that return nothing: “SPF implementations SHOULD limit “void lookups” to two.” and “Exceeding the limit produces a “permerror” result.” The tool counts a void when the TXT query for an include or redirect target returns NXDOMAIN or no TXT records at all. A target that publishes TXT records but no SPF record is not void, though it still fails (see below). More than two voids is FAIL, “N void lookups”. The a, mx and exists queries are never made, so their voids are not counted.
Include and redirect targets
Section titled “Include and redirect targets”Each target is looked up as in Record selection. The outcomes, and the rule behind each:
| Outcome | Finding | Severity | RFC section |
|---|---|---|---|
| No SPF record at the target | No SPF record at the include or redirect target: permerror. | FAIL | 5.2, 6.1 |
| Two or more SPF records | N SPF records: receivers return permerror. | FAIL | 4.5 |
| A name already being expanded higher in the same branch | Include loop | FAIL | none (Tamperbox; a loop exhausts 4.6.4) |
A name containing % |
SPF macros need a sender IP and envelope identity | CHECK | 7 |
| More than ten levels, or more than 100 names | Expansion limit reached | CHECK | none (Tamperbox bound) |
| SERVFAIL, timeout, truncated or inconclusive answer, or a name the server refuses | Lookup incomplete: retry before using this result. | CHECK | 4.4 |
For an include, section 5.2 maps a recursive “none” to “return permerror”; for a redirect, section 6.1 says “if no SPF record is found, or if the <target-name> is malformed, the result is a “permerror” rather than “none”.”
How branch findings are reported
Section titled “How branch findings are reported”A branch error appears as “domain: reason”. Failing findings inside an included record (syntax errors, broken targets) are carried up to the result; CHECK findings inside it are not, and the hygiene rules below run on the top record only. An incomplete branch adds “Incomplete lookup tree” (CHECK) and every count is shown as “at least N”.
The all mechanism and record hygiene
Section titled “The all mechanism and record hygiene”These run on the top record. Section 4.7 sets the default: “If none of the mechanisms match and there is no “redirect” modifier, then the check_host() returns a result of “neutral”, just as if “?all” were specified as the last directive.” For ptr, section 5.5: “This mechanism SHOULD NOT be published.”
| Finding | Severity | Rule | RFC section |
|---|---|---|---|
| No all mechanism | CHECK | No all and no redirect |
4.7 |
| +all allows every server on the internet | FAIL | +all or bare all |
none (Tamperbox; the RFC allows it) |
| ?all makes SPF neutral | CHECK | ?all |
4.7 |
| Ends in ~all, Ends in -all | PASS | ~all or -all |
none (Tamperbox) |
| Mechanisms after all are ignored | CHECK | A mechanism follows all |
5.1 |
| redirect is ignored when all is present | CHECK | Both present | 6.1 |
| ptr is deprecated | CHECK | Any ptr |
5.5 |
| Duplicate X | CHECK | The same term twice, compared exactly; a leading + is ignored |
none (Tamperbox) |
| N characters | INFO | Longer than one 255-character TXT string | 3.3 |
Flattening plan
Section titled “Flattening plan”The plan is a Tamperbox method; RFC 7208 neither requires nor describes flattening, though section 10.1.1 calls a record of ip4 terms the “Best record” for DNS cost. It appears when the total is over 10.
Candidates are the top record’s includes with a pass qualifier. One is flattenable only when its whole subtree holds nothing but pass-qualified ip4 and ip6 terms, pass-qualified nested includes and a non-pass all, every lookup in it succeeded, and it has no failing problems. That condition keeps the result equivalent under section 5.2: an include matches only when the included record returns pass, so its pass ranges are exactly what it contributes, and a -all inside it means “not match”, not fail.
Candidates are taken in order of savings (the include plus everything beneath it) until the total is 10 or lower. No plan is chosen when the top record has failures or the tree is incomplete. Each chosen include is replaced in place by its ranges, so they stay ahead of any - or ~ mechanism that follows. The flattened record is split into 255-character strings. If every candidate flattened still leaves it over 10, the plan says so and suggests a subdomain.
An include that cannot be flattened is labelled “Keep: uses a, mx or exists” whatever the reason: it may also use ptr, redirect or a non-pass term, or have failed to resolve. A flattenable include the plan does not need is labelled “Keep: N lookups”.
Standards and conformance
Section titled “Standards and conformance”Where each part of the work happens: the server only asks public DNS (Cloudflare’s resolver, over DNS over HTTPS) for TXT records and returns the answers. Parsing, the lookup count, the include tree, every finding and the flattening plan are computed in the browser from those answers, by static evaluation: no sender address is tested. A pasted record is parsed in the browser; names under the reserved .example domain are answered from synthetic data in the browser.
RFC 7208: records and selection
Section titled “RFC 7208: records and selection”| Section | Requirement | What the tool does |
|---|---|---|
| 3.1 | SPF records are TXT records only | Queries TXT only |
| 3.2, 4.5 | One record per name; more is permerror | FAIL, then parses the first record |
| 3.3 | Multiple strings concatenated without spaces | Joins quoted strings before parsing; the flattened record is split back into 255-character strings |
| 3.4 | Answers SHOULD fit in 512 octets (450 for the records) | Not checked |
| 3.5 | Wildcard records are discouraged | Not checked |
| 4.3 | NXDOMAIN gives “none”; IDNs MUST be A-labels | NXDOMAIN is “No SPF record”; names are converted to A-labels before lookup |
| 4.4 | DNS errors give temperror | Marks the branch incomplete (CHECK) |
| 4.5 | Keep records starting with exactly “v=spf1” | Same; v=spf10 is discarded |
RFC 7208: syntax
Section titled “RFC 7208: syntax”| Section | Requirement | What the tool does |
|---|---|---|
| 4.6 | Any syntax error is permerror | Every error is a FAIL finding; all are listed, not only the first |
| 4.6.1 | Eight mechanisms, name=value modifiers, names case-insensitive |
Implemented; terms split on any whitespace |
| 5.1 | all takes no value |
FAIL on a value |
| 5.2, 5.7 | include and exists need a domain-spec |
FAIL when empty; the domain-spec itself is not validated |
| 5.6 | ip4 and ip6 networks and prefix lengths |
Addresses validated; prefix ranges checked |
| 6 | redirect and exp at most once; unknown modifiers ignored |
Duplicate redirect or exp is FAIL; repeated unknown modifiers are ignored for validity |
| 7.1 | Macro and domain-spec grammar | Not validated |
RFC 7208: evaluation and limits
Section titled “RFC 7208: evaluation and limits”| Section | Requirement | What the tool does |
|---|---|---|
| 4.1 | One global lookup limit across recursion | One total for the tree |
| 4.6.2 | Mechanisms left to right; stop at the first match | Left to right; no match is simulated, so every term is counted |
| 4.6.4 | At most 10 DNS-querying terms | Counted statically; FAIL above 10 |
| 4.6.4 | At most 10 address records per MX or PTR record | Not checked (no address queries) |
| 4.6.4 | SHOULD limit void lookups to two | Counts TXT voids only; FAIL above two |
| 4.7 | No match and no redirect gives neutral | CHECK “No all mechanism” |
| 5.1, 6.1 | Ignore terms after all; ignore redirect when all is present |
Same order and exclusions |
| 5.2 | include of a domain without SPF is permerror | FAIL |
| 5.5 | ptr SHOULD NOT be published |
CHECK |
| 6.1 | redirect to a domain without SPF is permerror | FAIL |
RFC 7208: not evaluated
Section titled “RFC 7208: not evaluated”The tool reads records; it does not run check_host() for a connection. Not covered:
- the
<ip>and<sender>arguments of section 4.1, so no pass, fail or softfail result is produced for any address - the address, MX, PTR and A queries of
a,mx,ptrandexists(sections 5.3 to 5.7), and the MX and PTR sub-limits of section 4.6.4 - macro expansion (section 7): a branch whose name holds
%is left unexpanded - the explanation lookup of
exp(section 6.2) - the HELO identity (section 2.3), result handling (section 8) and the Received-SPF header (section 9)
- the elapsed-time limit of section 4.6.4 (the server stops each lookup after 8 seconds instead)
Stricter than the RFC
Section titled “Stricter than the RFC”- The count covers every path. Receivers stop at the first match, so mail matched early can pass a record the tool scores as over the limit.
- +all is FAIL. RFC 7208 allows it; it makes SPF meaningless, so the tool treats it as broken.
- Includes in a record with syntax errors are still expanded and counted. A receiver returns permerror before looking any of them up.
More lenient than the RFC
Section titled “More lenient than the RFC”- Separators. Terms split on any whitespace; section 4.6.1 allows only spaces.
- Prefix lengths with leading zeros such as
/024are accepted; the ABNF in section 5.6 rejects them. - Malformed targets. A domain-spec is not checked against section 7.1. A malformed include or redirect target is refused by the server and shown as an incomplete branch (CHECK), where receivers return permerror.
- DNS errors are shown as incomplete (CHECK), not as the temperror of section 4.4.
- Void lookups are counted for TXT queries only, so a record whose
aormxnames do not exist may pass the void check here and fail at a receiver. - U-labels in a record’s include or redirect target are converted to A-labels when looked up, not reported; section 4.3 says IDNs in a record “MUST be encoded as A-labels”.
Updates to RFC 7208
Section titled “Updates to RFC 7208”RFC 7208 is a Proposed Standard, updated by three documents:
- RFC 8616, section 4: “All U-labels MUST be converted to A-labels before being used for an SPF validation.” A domain typed with non-ASCII labels is converted to A-labels before it is looked up. Its rule on the
%{s}and%{l}macros does not apply, since macros are not expanded. - RFC 7372, section 3.2: SMTP enhanced status codes for SPF failures. Not relevant: the tool sends no mail.
- RFC 8553, section 2.1: registers underscored TXT names. It changes nothing the tool checks.
RFC 9989: how SPF feeds DMARC
Section titled “RFC 9989: how SPF feeds DMARC”The findings that mention DMARC follow RFC 9989, which obsoleted RFC 7489 in May 2026. The tool does not evaluate DMARC; it uses these sections to explain consequences:
- Section 4.4.2: “DMARC relies solely on SPF validation of the MAIL FROM identity”, and only an aligned SPF pass counts.
- Section 5.3.5: a message fails DMARC when no authenticated identifier aligns. A broken SPF record therefore leaves DKIM as the only path to a DMARC pass.
- Section 7.1:
-allcan get mail rejected before DMARC runs, even mail an aligned DKIM signature would pass. This is why the guide suggests~allwhile DMARC is being rolled out.
RFC 1035: TXT strings
Section titled “RFC 1035: TXT strings”RFC 1035 section 3.3 defines a character-string as one length octet followed by at most 255 characters, and section 3.3.14 defines TXT data as one or more of them. That is the “255-octet maximum length of a character-string” RFC 7208 section 3.3 refers to, and the basis for the INFO finding and the 255-character split of the flattened record. The tool counts characters, which equals octets for the ASCII text section 3.1 requires.
Finding reference
Section titled “Finding reference”Each finding the checker can emit, grouped by what it judges, with what it means for mail sent as the domain and the RFC section behind it. “none” marks a Tamperbox recommendation rather than an RFC rule.
Lookup limits
Section titled “Lookup limits”The budget RFC 7208 section 4.6.4 sets: ten DNS-querying terms and two void lookups per evaluation.
| Finding | Severity | What it means for mail | Fix | RFC section |
|---|---|---|---|---|
| N of 10 DNS lookups (11 or more) | FAIL | Evaluations reaching the eleventh lookup return permerror; under DMARC that mail can fail | Remove unused includes, flatten, or move a sender to a subdomain | 4.6.4 |
| N of 10 DNS lookups (9 or 10) | CHECK | The next include breaks the record | Make room before adding a service | none |
| N of 10 DNS lookups (8 or fewer) | PASS | Within the limit | 4.6.4 | |
| N void lookups | FAIL | More than two empty answers is a permanent error | Remove includes whose target has no record | 4.6.4 |
Record and term syntax
Section titled “Record and term syntax”Problems that make the record itself unusable or ambiguous to a receiver.
| Finding | Severity | What it means for mail | Fix | RFC section |
|---|---|---|---|---|
| No record at domain. | FAIL | Nothing is published at the name, so SPF never passes | Publish one record | 4.3, 4.5 |
| No SPF record at this name. | FAIL | SPF never passes; DMARC relies on DKIM alone | Publish one record | 4.5 |
| An SPF record starts with v=spf1. | FAIL | The text is not an SPF record | Start with v=spf1 | 4.5 |
| N SPF records: receivers return permerror. | FAIL | Every message fails SPF | Merge into one record | 4.5 |
| Unknown term X | FAIL | Permanent error for every receiver | Correct the term | 4.6.1 |
| Invalid IPv4 address in X, Invalid IPv6 address in X | FAIL | Permanent error | Correct or remove the term | 5.6 |
| Prefix too long in X, Invalid prefix in X | FAIL | Permanent error | Use a valid prefix | 5.6 |
| include needs a domain, exists needs a domain, redirect needs a domain, exp needs a domain | FAIL | Permanent error | Add the domain | 5.2, 5.7, 6.1, 6.2 |
| Invalid all mechanism X | FAIL | all takes no value |
Remove the value | 5.1 |
| Duplicate X modifier | FAIL | Permanent error for redirect and exp; other modifiers may repeat under the RFC |
Remove one | 6 |
| Unknown modifier X | CHECK | Receivers ignore it | Remove it | 6 |
The all mechanism and redirect
Section titled “The all mechanism and redirect”How the record ends decides what happens to servers it does not list.
| Finding | Severity | What it means for mail | Fix | RFC section |
|---|---|---|---|---|
| No all mechanism | CHECK | Unlisted servers get neutral; SPF never fails | Add ~all or -all | 4.7 |
| +all allows every server on the internet | FAIL | Anyone passes SPF for the domain | Use ~all or -all | none |
| ?all makes SPF neutral | CHECK | Unlisted servers are neither allowed nor refused | Use ~all or -all | 4.7 |
| Ends in ~all, Ends in -all | PASS | Unlisted servers soft-fail or fail | none | |
| Mechanisms after all are ignored | CHECK | Those senders are not allowed | Move all to the end | 5.1 |
| redirect is ignored when all is present | CHECK | The redirect never runs | Remove one | 6.1 |
Costly or redundant terms
Section titled “Costly or redundant terms”Terms that work but spend lookups or bytes for nothing.
| Finding | Severity | What it means for mail | Fix | RFC section |
|---|---|---|---|---|
| ptr is deprecated | CHECK | Slow, unreliable, skipped by some receivers | Replace with ip4/ip6 | 5.5 |
| Duplicate X | CHECK | The copy costs a lookup | Remove one | none |
| N characters | INFO | Longer than one TXT string | Publish as several quoted strings | 3.3 |
Include and redirect targets
Section titled “Include and redirect targets”Findings about the records the tool follows, prefixed with the name they were found at.
| Finding | Severity | What it means for mail | Fix | RFC section |
|---|---|---|---|---|
| Incomplete lookup tree | CHECK | Counts are a lower bound | Retry before changing DNS | 4.4 |
| domain: No SPF record at the include or redirect target: permerror. | FAIL | The include breaks the record | Remove the include | 5.2, 6.1 |
| domain: N SPF records: receivers return permerror. | FAIL | The include breaks the record | Ask the provider to merge them | 4.5 |
| domain: Include loop | FAIL | The include refers back to a record above it | Break the loop | none |
| domain: Expansion limit reached | CHECK | Too deep or too many names to inspect | Simplify the record | none |
| domain: SPF macros need a sender IP and envelope identity | CHECK | The branch is not expanded | Nothing; evaluate with a real sender | 7 |
| domain: Lookup incomplete: retry before using this result. | CHECK | The branch was not read | Retry | 4.4 |
Too many DNS lookups
Section titled “Too many DNS lookups”- Open the include tree and find the branches that cost the most; the lookup budget numbers them in order.
- Remove the includes of services that no longer send for you. This alone fixes most records.
- If the record is still over 10, open the flattening plan. Heal it loads the flattened record; copy it from the Flattened record box.
- Publish the new record as the only
v=spf1TXT record at the domain, then check the domain again. The count should be 10 or lower. - Put a reminder in the calendar: flattened ranges are a snapshot. Check the flattened includes on their own page every few months, or after the provider announces a change.
A sender on a subdomain with its own SPF record never counts against the main domain; that is the lasting fix for a bulk mailer.
+all, no all, ?all
Section titled “+all, no all, ?all”End the record in ~all while DMARC is being rolled out and in -all once every sender is listed. Heal it changes +all to ~all and adds ~all to a record that has none; publish the result.
A typo or an invalid address
Section titled “A typo or an invalid address”One unknown mechanism or one bad address fails the whole record for every receiver. Heal it corrects common misspellings of include and removes an invalid ip4 or ip6 mechanism while keeping the valid ones; check the result and publish it.
Replace ptr with the ip4 and ip6 ranges of the servers it covered. Heal it removes ptr; add the ranges before publishing.
Two SPF records
Section titled “Two SPF records”Heal it merges them: v=spf1 once, every term from both, and -all if either record had it, ~all otherwise. Publish the merged record and delete the others.
An include that cannot be evaluated
Section titled “An include that cannot be evaluated”Open the include on its own (Take it further) to see what it publishes. If the service no longer sends for you, remove the include; if it does, ask the provider to fix its record.
Break it cases
Section titled “Break it cases”Each case is built from the record you entered or looked up, so the finding looks the way it would for you.
| Case | What it does to your record | Heal it offers |
|---|---|---|
| Two SPF records | Adds a second v=spf1 record |
Merge into one record |
| +all | Replaces the all mechanism with +all |
Change +all to ~all |
| No all at the end | Removes the all mechanism | Add ~all at the end |
| Invalid address | Inserts ip4:198.51.100.300 |
Remove invalid address mechanism |
| ptr mechanism | Inserts ptr |
Remove ptr |
| Duplicate include | Repeats the first include | Remove 1 duplicate |
| Typo in a mechanism | Misspells the first include as inlcude: |
Fix inlcude to include |
Worked example
Section titled “Worked example”The page opens on a live check of tamperbox.dev. Its published records and findings can change over time.
For the deterministic walkthrough below, enter harbor-supply.example. This reserved .example domain is answered from synthetic data in your browser; it sends no live lookup. Its record includes four mail services:
v=spf1 include:_spf.inboxcloud.example include:send.bulkpost.example include:mail.ticketdesk.example include:spf.officesuite.example ip4:198.51.100.24 ~allThe expansion costs 11 lookups: the first include nests three more, the second one more, the third uses a and mx, the fourth nests one. SPF lookup budget exceeded: 11 of 10 DNS lookups. The plan flattens _spf.inboxcloud.example (saves 4, 6 ranges) and keeps the rest: mail.ticketdesk.example because it uses a and mx, the other two because flattening them is not needed. The flattened record costs 7 lookups and fits in one TXT string. Heal it loads it; replace the sample with your own domain and every section, card and link is rewritten for it.
For agents
Section titled “For agents”The Tamperbox MCP server runs the same checks. Search its catalog for the SPF operation, inspect the input schema, and call it with the domain or the record. Keep the warning block with any conclusion:
- The count is a static expansion, not an evaluation for a sender address; mail matched by an earlier term can pass a record that is over the limit.
a,mxandexiststargets are not resolved; macros are not expanded.- Flattened ranges are a snapshot of what a provider publishes today.
See Connect an MCP client and Code mode for the calling pattern, and Security and privacy for where the domain travels.
Limits
Section titled “Limits”- A bounded static expansion: ten levels, 100 names, no sender address, no macros, no
a,mxorexistsresolution. - Void lookups are counted only for the TXT queries the tool makes.
- DNS errors and malformed target names are reported as an incomplete tree, not as the temperror or permerror a receiver would return; see Standards and conformance.
- Duplicate detection compares whole terms; an explicit
+is treated as the default qualifier. - One DNS answer, from one public resolver, at one moment.
- Nothing is stored.
References
Section titled “References”- RFC 7208, Sender Policy Framework (SPF) for Authorizing Use of Domains in Email, Version 1. Proposed Standard, updated by RFC 7372, RFC 8553 and RFC 8616. Sections used: 2.3, 3.1, 3.2, 3.3, 3.4, 3.5, 4.1, 4.3, 4.4, 4.5, 4.6, 4.6.1, 4.6.2, 4.6.4, 4.7, 5.1, 5.2, 5.5, 5.6, 5.7, 6, 6.1, 6.2, 7, 7.1, 8, 9, 10.1.1
- RFC 8616, Email Authentication for Internationalized Mail. Proposed Standard, updates RFC 7208. Section used: 4
- RFC 7372, Email Authentication Status Codes. Proposed Standard, updates RFC 7208. Section noted: 3.2
- RFC 8553, DNS AttrLeaf Changes. Best Current Practice (BCP 222), updates RFC 7208. Section noted: 2.1
- RFC 9989, Domain-based Message Authentication, Reporting, and Conformance (DMARC). Standards Track, May 2026; obsoletes RFC 7489 and RFC 9091. Sections used: 4.4.2, 5.3.5, 7.1
- RFC 1035, Domain Names: Implementation and Specification. Internet Standard. Sections used: 3.3, 3.3.14
- Tamperbox guides: Fix SPF PermError: too many DNS lookups and DMARC rollout: from p=none to reject