Methodology, sources and tests
What the analyser does with the text you paste, what it deliberately can't do, the open data behind it, and the test results from this build.
How an analysis works
- Find the header block. The paste is split at blank lines and the block with the most recognisable header fields is used, so a whole message source, a Gmail "Show original" page copied with its summary table, or an mbox file all work. Input is capped at 1,000,000 characters and 3,000 fields.
- Unfold folded lines (a line break followed by a space or tab is removed, RFC 5322 §2.2.3) and decode encoded-words in names and subjects (RFC 2047: B and Q encodings, any charset the browser knows).
- Parse each field with small hand-written scanners that understand quoted strings and nested comments (RFC 5322 §3.2.2): addresses, dates (including the obsolete forms of §4.3: two-digit years, named zones like EST and PDT), Received clauses (from, by, with, id, for and the date after the last semicolon, RFC 5321 §4.4), Authentication-Results (RFC 8601), ARC (RFC 8617), Received-SPF (RFC 7208 §9.1) and DKIM-Signature tags (RFC 6376 §3.5). Every scanner is linear: no regular expression can backtrack over a long field.
- Route. Received fields are read bottom-up, every timestamp is converted to UTC, and the gap between consecutive hops is computed. Gaps of 5 minutes or more are highlighted, an hour or more in red; a hop that seems to go back in time by more than 2 minutes is labelled clock skew. The last hop where the sending and receiving hosts belong to different organisations is marked as where your provider took the message in: its IP address was observed by the receiving server, unlike anything below it.
- Authentication. Only the topmost Authentication-Results field's server (the authserv-id) is treated as your provider; fields from other servers lower down are listed separately, because a sender can write anything there (RFC 8601 §5). Microsoft 365 writes its field without a server name; when such a field is on top it is used too.
- Alignment. The From domain is compared with the domain SPF checked (
smtp.mailfrom, else Received-SPF's envelope-from, else Return-Path) and each DKIM signing domain (header.d). Relaxed alignment means the same organisational domain, strict means identical (RFC 9989 §3.2.10). - Red flags and verdict from the rules below.
What it can't do
- No network. The page's security policy (
connect-src 'self') only lets it load its own files, and the analyser makes no requests at all. So it can't look up SPF, DKIM or DMARC records or verify signatures itself. It reports what the receiving server recorded at delivery time, which is the evidence that matters: records can change afterwards. - Organisational domains are approximate. DMARC (RFC 9989 §4.10) now finds the organisational domain with a DNS "tree walk"; without DNS, MailDingo uses the Public Suffix List, as the original DMARC specification (RFC 7489 §3.2) did. For ordinary domains the answer is identical.
- It can't see strict-mode or policy records. Alignment is shown in relaxed mode with a note; a domain may require strict alignment.
- Headers below your provider's can be forged. The tool says which parts were written by whom, but it can't prove the lower Received lines are true.
- A compromised real account passes everything. No header analysis can detect a genuine mailbox used by an intruder.
Red-flag rules
| Flag | Severity | Raised when |
|---|---|---|
| DMARC failed | high | Your provider recorded dmarc=fail and no arc=pass. |
| DMARC failed, forwarder vouches | medium | dmarc=fail together with arc=pass. |
| SPF fail/softfail and no valid DKIM | high (fail) / medium (softfail) | No DKIM pass to compensate, and no DMARC result already covering it. |
| Display name contains a different address | high | The name part of From contains an email address other than the real one (medium-low if it's the same organisation). |
| Display name names another domain / mixes alphabets | medium | A domain-like word in the name that isn't the From organisation; Latin mixed with Cyrillic, Greek or other scripts. |
| From domain mixes alphabets | high | Any label of the From domain (after punycode decoding, RFC 3492) contains letters from more than one script. |
| From domain imitates another | high | The From domain's skeleton (below) equals that of another domain in the message (Reply-To, Return-Path, Sender, DKIM, Received, To, the display name) belonging to a different organisation. |
| International (punycode) From domain | medium | Contains xn-- labels or raw Unicode, without mixed scripts. |
| Replies go to a different domain | medium (low on mailing lists) | Reply-To's organisation differs from From's. |
| Return-Path doesn't match From | medium / note | Different organisations and no aligned DKIM pass; only a note when DKIM aligns or a list/forwarder is involved. |
| No authentication results | medium | No Authentication-Results and no Received-SPF field at all. |
| Date later than delivery / long before it | low | Date header more than 15 minutes after the first Received time, or more than 2 days before it. |
| Encoded name for no reason | low | The From name is MIME-encoded but decodes to plain ASCII. |
Partial DKIM body (l=) | low | A DKIM-Signature carries l= (RFC 6376 §8.2). |
| Slow hop / clock skew | low / note | A hop gap of an hour or more (5 minutes or more is a note) / a negative gap over 2 minutes. |
Verdict: any high flag gives Suspicious. Otherwise a DMARC pass (or, with no DMARC result, an aligned SPF or DKIM pass) and no medium flags gives Looks genuine; with medium flags, Check carefully. No authentication data at all gives Can't tell.
Look-alike detection
Domains are decoded from punycode, then each character is mapped through the Unicode confusables data (Unicode Technical Standard #39, version 18.0.0), keeping the 1,787 mappings whose target is one to three ASCII letters or digits, plus Unicode compatibility decomposition (so fullwidth e becomes e). This is a simplified skeleton: real UTS #39 also maps multi-character sequences and non-Latin targets. On top of that, the common ASCII swaps rn→m, vv→w, cl→d, 0→o, 1→l and 5→s are folded. Mixed-script detection checks Latin, Cyrillic, Greek, Armenian, Cherokee, Han, Arabic and Hebrew per label (UTS #39 §5.1, simplified).
Sending-service rules
A host, DKIM signing domain, Return-Path or Message-ID domain that equals or ends in one of these domains names the service. It's a static, documented table, not a guess: a name not on it is simply not identified.
| Service | Kind | Matched domains |
|---|---|---|
| Google (Gmail / Google Workspace) | mailbox | google.com, googlemail.com, gmail.com |
| Microsoft (Outlook.com / Exchange Online) | mailbox | outlook.com, protection.outlook.com, hotmail.com, office365.com, onmicrosoft.com, outlook.office365.com |
| Yahoo / AOL Mail | mailbox | yahoo.com, yahoodns.net, aol.com |
| Apple iCloud Mail | mailbox | icloud.com, me.com, mac.com |
| Proton Mail | mailbox | protonmail.ch, proton.me, protonmail.com |
| Fastmail | mailbox | messagingengine.com, fastmail.com |
| Zoho Mail | mailbox | zoho.com, zohomail.com, zoho.eu |
| Amazon SES | sending service | amazonses.com |
| SendGrid (Twilio) | sending service | sendgrid.net |
| Mailchimp / Mandrill | sending service | mcsv.net, mcdlv.net, rsgsv.net, mandrillapp.com |
| Mailgun | sending service | mailgun.org, mailgun.net |
| Postmark | sending service | mtasv.net, postmarkapp.com |
| SparkPost | sending service | sparkpostmail.com |
| Brevo (Sendinblue) | sending service | sendinblue.com, brevo.com |
| HubSpot | sending service | hubspotemail.net |
| Salesforce Marketing Cloud | sending service | exacttarget.com |
| Mimecast | security gateway | mimecast.com |
| Proofpoint | security gateway | pphosted.com |
| Barracuda | security gateway | barracudanetworks.com |
| Cisco Secure Email | security gateway | iphmx.com |
Test results for this build
Every weekly build runs the test suite under Node.js v20.19.2 against the exact mailcheck.js file your browser downloads. If any test fails, the build stops and the previous version of the site stays online. Run on 2026-11-01.
| Group | What it covers | Passed |
|---|---|---|
| unfold | Unfolding folded header lines (RFC 5322 §2.2.3), line endings, header-block detection | 12/12 |
| encoded | Encoded-words (RFC 2047): B and Q encodings, charsets, adjacent words, malformed input | 12/12 |
| dates | Date parsing (RFC 5322 §3.3) including obsolete forms (§4.3), named zones and odd formats | 21/21 |
| received | Received field parsing: Postfix, Exim, Gmail, Exchange Online, SendGrid, IPv6, TLS, missing parts | 16/16 |
| authres | Authentication-Results (RFC 8601), ARC-Authentication-Results (RFC 8617) and vendor variants | 10/10 |
| address | Address parsing: display names, quoting, encoded names, groups, comments | 10/10 |
| domains | Organisational domains via the Public Suffix List, punycode (RFC 3492) | 11/11 |
| lookalike | Look-alike domains: mixed scripts, Unicode confusables skeleton (UTS #39), ASCII swaps | 10/10 |
| providers | Sending-service identification from the documented hostname table | 7/7 |
| hostile | Hostile and malformed input: huge pastes, pathological nesting; every case must finish quickly and render escaped HTML | 15/15 |
| registry | Every active IANA result code for spf, dkim, dmarc, arc, iprev and auth has a plain-English explanation | 32/32 |
| examples | Synthetic example messages: expected verdicts, flags, delays and alignment | 50/50 |
Hostile input
Each case must be parsed and rendered in under 3 seconds without throwing, and the output must contain no unescaped markup. Times from this build:
| Input | Size | Time |
|---|---|---|
| 1 MB single line, no newline | 1,000,010 chars | 44 ms |
| 2 MB paste is truncated to 1 MB | 2,054,011 chars | 21 ms |
| 100,000 nested parentheses | 100,054 chars | 2 ms |
| 100,000 unbalanced quotes | 100,021 chars | 7 ms |
| 50,000 encoded-word fragments | 500,009 chars | 5 ms |
| 20,000 valid encoded-words | 280,009 chars | 38 ms |
| 10,000 Received fields | 1,170,000 chars | 15 ms |
| 100,000 continuation lines | 300,017 chars | 39 ms |
| random binary bytes | 300,000 chars | 0 ms |
| 10,000 semicolons in Authentication-Results | 100,038 chars | 3 ms |
| long IPv6-ish token | 100,056 chars | 1 ms |
| many '@' in From | 200,006 chars | 2 ms |
| PSL walk on 10,000-label domain | 20,011 chars | 6 ms |
Plain-English explanations, checked against the RFCs
Each one-line explanation the analyser shows was written from, and checked by hand against, the section that defines the result. The list of result codes is read from IANA's registry every week; the build fails if a registered code for these methods has no explanation.
| Result | Defined in | What MailDingo says |
|---|---|---|
| arc=fail | RFC 8617 §4.4, §5.2 (via RFC 8617 §2.2) | The ARC chain is broken: a seal did not verify, so earlier servers' results can't be relied on. |
| arc=none | RFC 8617 §4.4, §5.2 (via RFC 8617 §2.2) | No ARC chain: the message was not sealed by any intermediary. |
| arc=pass | RFC 8617 §4.4, §5.2 (via RFC 8617 §2.2) | The ARC chain is intact: every forwarding server's seal verified. |
| auth=fail | RFC 8601 §2.7.4 | The sender tried to log in and failed. |
| auth=none | RFC 8601 §2.7.4 | No login was used. |
| auth=pass | RFC 8601 §2.7.4 | The sender logged in to submit this message. |
| auth=permerror | RFC 8601 §2.7.4 | A permanent error during login checking. |
| auth=temperror | RFC 8601 §2.7.4 | A temporary error during login checking. |
| dkim=fail | RFC 8601 §2.7.1 | A signature was present but did not verify: the message was changed after signing, or the signature is forged. |
| dkim=neutral | RFC 8601 §2.7.1 | A signature was present but had syntax errors or couldn't be processed, so the message is treated as unsigned. |
| dkim=none | RFC 8601 §2.7.1 | The message was not DKIM-signed. |
| dkim=pass | RFC 8601 §2.7.1 | The signature verified: the signing domain really handled this message and the signed content is unchanged. |
| dkim=permerror | RFC 8601 §2.7.1 | The signature can't ever be verified because of a permanent problem, such as a missing key or a required header field that isn't there. |
| dkim=policy | RFC 8601 §2.7.1 | The message was signed, but something about the signature wasn't acceptable to the receiver (for example a weak key, an outdated algorithm or a body-length limit). |
| dkim=temperror | RFC 8601 §2.7.1 | The signing key couldn't be fetched because of a temporary DNS problem. |
| dmarc=fail | RFC 9989 §9.2 | Neither SPF nor DKIM passed for a domain aligned with the From domain, and that domain publishes DMARC: the From address is not authenticated. |
| dmarc=none | RFC 9989 §9.2 | The From domain publishes no DMARC record, so the receiver had no way to judge the From address. |
| dmarc=pass | RFC 9989 §9.2 | SPF or DKIM passed for a domain that aligns with the visible From domain: the From address is authentic. |
| dmarc=permerror | RFC 9989 §9.2 | The From domain's DMARC record is broken and can't be used. |
| dmarc=temperror | RFC 9989 §9.2 | A temporary DNS problem stopped the DMARC check. |
| iprev=fail | RFC 8601 §2.7.3 | The sending IP's reverse DNS name doesn't point back to it (or doesn't exist when looked up forward); common on badly run servers and spam sources. |
| iprev=pass | RFC 8601 §2.7.3 | The sending IP's reverse DNS name points back to it. |
| iprev=permerror | RFC 8601 §2.7.3 | The sending IP has no reverse DNS name at all. |
| iprev=temperror | RFC 8601 §2.7.3 | A temporary DNS problem stopped the reverse DNS check. |
| spf=fail | RFC 7208 §2.6 (via RFC 8601 §2.7.2) | The envelope domain says this server is NOT allowed to send its mail (a hard "-all" fail). |
| spf=neutral | RFC 7208 §2.6 (via RFC 8601 §2.7.2) | The envelope domain's SPF record makes no statement about this server ("?all"). |
| spf=none | RFC 7208 §2.6 (via RFC 8601 §2.7.2) | The envelope domain publishes no SPF record (or no domain could be checked), so nothing was verified. |
| spf=pass | RFC 7208 §2.6 (via RFC 8601 §2.7.2) | The sending server is on the envelope domain's list of permitted senders. |
| spf=permerror | RFC 7208 §2.6 (via RFC 8601 §2.7.2) | The domain's SPF record is broken (syntax error or too many DNS lookups), so it can't be evaluated. |
| spf=policy | RFC 8601 §2.7.2 | The server was authorised by SPF, but the receiver's own policy found the result unacceptable (for example, the domain is on its block list). Rarely used. |
| spf=softfail | RFC 7208 §2.6 (via RFC 8601 §2.7.2) | The envelope domain says this server probably shouldn't send its mail ("~all"), but asks receivers not to reject outright. |
| spf=temperror | RFC 7208 §2.6 (via RFC 8601 §2.7.2) | A temporary DNS problem stopped the check; a retry might give a real answer. |
Sources and licences
- IETF RFCs (rfc-editor.org): the facts in every guide, cited by section. Text is our own.
- Public Suffix List, version 2026-10-31_14-17-28_UTC, Mozilla Public License 2.0. Served unmodified in content (reformatted as JSON) at /static/data/psl.json; original at publicsuffix.org; licence text.
- Unicode confusables.txt, version 18.0.0, Unicode License v3 (permits commercial use with the notice). A filtered subset is served at /static/data/confusables.json; licence and copyright notice.
- IANA Email Authentication Parameters registry, updated 2026-05-22, dedicated to the public domain (CC0 1.0) by IANA and the IETF Trust.
- Data last refreshed: psl 2026-11-01, confusables 2026-11-01, iana 2026-11-01.
- Example messages are invented and use reserved names (RFC 2606, RFC 6761) and documentation addresses (RFC 5737, RFC 3849). Real provider hostnames appear only to show what their headers look like.
- Mail-client menu paths in the "find the headers" guides were checked against each vendor's own help pages where available; where a vendor doesn't document a step, the guide says so.
- The dingo and all illustrations are original drawings made for this site.