sitekits.dev
email

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

§01 FIELD GUIDE

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

MechanismPublished atAuthenticatesSurvives forwardingStops visible From: spoofing
SPFTXT on the envelope domain, v=spf1 …Connecting IP for MAIL FROMNo — the relay IP is not listedNo
DKIMTXT at <selector>._domainkey.<d=>Headers in h= + body hash bh=Yes, unless the body or Subject is alteredOnly if aligned
DMARCTXT at _dmarc.<domain>From: alignment with SPF or DKIM, plus policyYes, through the surviving DKIMYes — that is its entire purpose
ARCARC-Seal / ARC-Message-Signature headersPreserves upstream results across a relayDesigned for exactly thisNo — and only trusted sealers count
MTA-STS / DANETXT at _mta-sts.<domain> / TLSATransport: that TLS was mandatoryn/aNo

The result tokens are shared vocabulary across all three checks, and each points at a different fix:

TokenMeaningUsual cause
passCheck succeeded
failNot authorised, hard (-all)Spoof, or a sending relay missing from the record
softfailNot authorised, mark only (~all)Mid-rollout record left permissive
neutralExplicitly no opinion (?all)Policy states nothing useful
noneNo record publishedDomain never configured SPF or DMARC
temperrorDNS failure, retryableResolver or authoritative server flapping
permerrorRecord 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.

FAQ
Is SPF enough to stop someone spoofing my domain?
No. SPF only authorises the envelope sender (the SMTP MAIL FROM), which the recipient never sees. An attacker can pass SPF on a domain they control while putting your domain in the visible From: header. Only DMARC ties the two together by requiring alignment between From: and the SPF or DKIM domain, and only p=quarantine or p=reject makes receivers act on a failure.
Why does a legitimate email fail SPF after it is forwarded?
Forwarding changes the connecting IP, so the receiving server checks the forwarder's IP against your SPF record and finds it unlisted. DKIM usually survives because it signs headers and body content rather than the path — unless a mailing list rewrites the Subject or appends a footer, which breaks the body hash. This is why DMARC accepts a pass from either mechanism, and why ARC exists to carry the original verdict across a trusted relay.
Does a domain that never sends mail still need SPF and DMARC?
Yes — an unprotected parked domain is free spoofing material carrying your brand, and attackers look for exactly those. Publish v=spf1 -all, a DMARC record at _dmarc.<domain> with p=reject, and ideally a null MX (MX 0 .) so inbound mail is refused outright. Watch the policy tag: p= is required, so a record carrying only sp=reject is invalid and receivers apply nothing at all. Adding sp=reject beside p= only states the subdomain policy explicitly — with sp= absent, subdomains inherit p= anyway.
What does dmarc=pass in Authentication-Results actually mean?
It means the receiving mail server verified that the domain in the From: header aligns with a domain that passed SPF or DKIM, and it applied the policy found at _dmarc.<domain>. It is the receiver's verdict written into the message after the fact, not a signature you can re-verify from the text. Trust it only when the hop that wrote it is your own boundary MTA.
Why does my SPF record report permerror instead of pass or fail?
permerror means the record could not be evaluated. The usual causes are exceeding the 10 DNS-lookup limit that include:, a, mx and redirect= consume, publishing two separate v=spf1 TXT records on the same name, or a syntax error such as a stray semicolon. Receivers treat permerror as no usable SPF, so a DMARC policy then depends entirely on DKIM.