Email tools
How email authentication really works: the two From addresses, what SPF, DKIM and DMARC each prove, how to read a Received chain, and which tool to use on headers or .eml.
3 tools
The two From addresses
Almost every email spoofing question comes down to confusing two sender
identities. The envelope sender is the address supplied in the SMTP
MAIL FROM command: bounces go there, and receivers record it as
Return-Path: or smtp.mailfrom= inside Received-SPF:. The header sender
is the From: field inside the message — the only one a human sees. Nothing in
SMTP requires the two to match.
SPF authorises the envelope: was the connecting IP allowed to send for the
MAIL FROM domain? DKIM authorises content: the sender signs a chosen list of
header fields (h=) plus a body hash (bh=), and publishes the public key in
DNS. Neither says anything about the address the recipient reads. DMARC closes
that gap by requiring alignment — the visible From: domain must match the
SPF domain or the DKIM d= domain. spf=pass on mail claiming to be from your
bank is entirely normal for a phish; dmarc=pass is not.
The third thing to internalise: you are not verifying any of this. The
receiving MTA runs the checks at delivery time and writes its verdict into an
Authentication-Results: header. After that, the result is a claim in a text
file — trustworthy only if the hop that wrote it belongs to your own
infrastructure. A header written by a stranger’s server could have been invented
by that stranger. Read outward from your boundary MTA and treat everything
beyond it as untrusted input.
What each mechanism proves
| Mechanism | Published at | Authenticates | Survives forwarding | Stops visible From: spoofing |
|---|---|---|---|---|
| SPF | TXT on the envelope domain, v=spf1 … | Connecting IP for MAIL FROM | No — the relay IP is not listed | No |
| DKIM | TXT at <selector>._domainkey.<d=> | Headers in h= + body hash bh= | Yes, unless the body or Subject is altered | Only if aligned |
| DMARC | TXT at _dmarc.<domain> | From: alignment with SPF or DKIM, plus policy | Yes, through the surviving DKIM | Yes — that is its entire purpose |
| ARC | ARC-Seal / ARC-Message-Signature headers | Preserves upstream results across a relay | Designed for exactly this | No — and only trusted sealers count |
| MTA-STS / DANE | TXT at _mta-sts.<domain> / TLSA | Transport: that TLS was mandatory | n/a | No |
The result tokens are shared vocabulary across all three checks, and each points at a different fix:
| Token | Meaning | Usual cause |
|---|---|---|
pass | Check succeeded | — |
fail | Not authorised, hard (-all) | Spoof, or a sending relay missing from the record |
softfail | Not authorised, mark only (~all) | Mid-rollout record left permissive |
neutral | Explicitly no opinion (?all) | Policy states nothing useful |
none | No record published | Domain never configured SPF or DMARC |
temperror | DNS failure, retryable | Resolver or authoritative server flapping |
permerror | Record cannot be evaluated | >10 DNS lookups, duplicate v=spf1, syntax error |
Which tool for which job
If you have the raw header block — “Show original” in Gmail, “View source”
elsewhere — start with the Email Header Analyzer. It unfolds
continuation lines, reverses the Received: stack so hop 1 is the originating
server and the last hop is your provider, and shows the seconds between adjacent
hops, so Delay: 1800s names the relay that sat on the message for half an
hour. Below the path it prints Authentication-Results,
Received-SPF, DKIM-Signature and ARC-Authentication-Results verbatim — the
DMARC verdict lives inside the first of those, as dmarc=pass (p=REJECT …).
Parsing stays in the browser, which is the point: those headers carry sender IPs
and internal hostnames.
With a whole .eml file instead — a bounce, a saved attachment, a form mailer’s
output — use the EML Email Viewer. It splits
headers from body, decodes RFC 2047 encoded-words (=?UTF-8?B?…?=) so
non-ASCII subjects read as text, and surfaces From, To, Cc, Subject,
Date, Reply-To and Message-ID. A Reply-To on a different domain from
From: is a classic business-email-compromise tell. The body is shown raw, so
MIME boundaries stay visible; run base64 parts through
Base64 Encode / Decode to read one.
With only a domain and no message, move to DNS. The
DNS Lookup Tool resolves TXT (your v=spf1 string), MX
(routing, with priority) and more, with the TTL that governs how long a fix
takes to take effect — the dns hub covers caching in depth. It is
a server tool: the domain name goes to api.sitekits.dev for resolution and is
not stored. It accepts hostname labels only, so underscore names such as
_dmarc.example.com and selector._domainkey.example.com are rejected — read
those with dig TXT _dmarc.example.com.
For links inside a suspect message, the URL Parser & Analyzer
breaks a URL into host, path and decoded query parameters without ever
requesting it, and the IDN / Punycode Converter exposes
homograph domains by showing the xn-- form that really gets resolved. Both
run in the browser, and both are curated on /for/security/
alongside the header analyzer and the .eml viewer. Hop timestamps in foreign
offsets line up faster with the Timezone Converter —
also browser-local, but collected on /for/sre/ rather than the
security page. Before attaching evidence to a ticket, redact by hand — the
HAR File Sanitizer covers HAR exports, not mail headers.
Where it usually goes wrong
p=none protects nothing
p=none only asks for reports. Publishing it is the right first step, but until
you reach p=quarantine or p=reject, receivers apply no policy on failure.
Check pct= too — a partial rollout enforces on that share of mail only.
Blowing the ten-lookup budget
include:, a, mx, ptr and redirect= each cost DNS lookups, and nested
include: chains from SaaS senders add theirs recursively. Cross ten and the
record becomes permerror, which receivers read as no SPF at all. Flatten it or
drop unused vendors.
Reading every failure as an attack
Forwarding and mailing lists break authentication legitimately: the relay IP
fails SPF, and a [list] subject prefix or appended footer invalidates the DKIM
body hash. Judge on alignment and the sealing relay, not a single fail.
Trusting hops you do not own
Only Received: and Authentication-Results: lines added at or after your
boundary MTA mean anything; earlier hops are attacker-controlled text. Delays
that come out negative or — are clock skew or a missing timestamp, not
evidence.
Leaving non-sending domains open
Parked domains and subdomains that never send mail still need v=spf1 -all, a
DMARC record at _dmarc.<domain> with p=reject, and ideally a null MX
(MX 0 .). p= is the required policy tag: a record carrying only sp=reject
is invalid, so receivers apply nothing and the parked domain itself stays
unprotected. Add sp=reject beside p= when you want the subdomain policy
stated explicitly — with sp= absent, subdomains inherit p= anyway. Left
open, the domain is free spoofing material carrying your brand.