Reading Email Headers: How to Trace Where a Message Really Came From
Every email carries a paper trail in its headers. Learn to read the Received chain from the bottom up, verify SPF/DKIM/DMARC, and find the true origin of a message even when the From line is lying to you.
EvilMail TeamJune 19, 202612 min read
# Reading Email Headers: How to Trace Where a Message Really Came From
The visible part of an email is theater. The From name, the pretty signature, the little "verified" badge some clients slap on — none of it is load-bearing. The truth lives above the fold, in a block of text most people never see: the headers. Every mail server that touched a message stamps its fingerprint there, in order, and that ordered pile of fingerprints is the closest thing email has to a chain of custody.
I spend a fair amount of time reading raw headers — chasing down why a legitimate newsletter landed in spam, confirming whether a "your account is locked" email is a phish, figuring out which of five relays mangled a message. Once you know the shape of the thing, it takes about ninety seconds. This is how I read them.
Getting the raw headers in the first place
Headers you can see in the client's "details" pane are usually filtered. You want the raw, unabridged version.
Gmail (web): open the message, three-dot menu, Show original. You get the full source plus a summary box with SPF/DKIM/DMARC verdicts.
Outlook (web):
three-dot menu →
View
→
View message source
.
Apple Mail: View → Message → All Headers (or Raw Source).
Thunderbird:Ctrl+U for source, or More → View Source.
Everything below assumes you're looking at that raw block. Headers are Name: value pairs, one per logical line (a value can wrap onto continuation lines that start with whitespace). They're stored top-to-bottom in reverse chronological order for the parts that get added in transit — which is the single most important thing to internalize, so let's start there.
The Received chain: read it from the bottom up
Each server that handles a message *prepends* a Received: header to the top. Prepend, not append. That means the oldest hop — the one closest to the real sender — is at the bottom of the stack, and the hop that delivered to your mailbox is at the top. New readers almost always read it the wrong way and end up blaming the wrong machine.
A single Received line has a rough grammar:
Received: from <HELO-name> (<reverse-DNS> [<IP address>])
by <receiving server> with <protocol>
id <queue id>;
<timestamp>
The part you care about most is the from clause. <HELO-name> is whatever the connecting server *claimed* to be when it said HELO — attacker-controlled, so treat it as a suggestion. The bracketed [IP address] is what the receiving server actually *observed* on the TCP connection. That IP cannot be faked without controlling the network path, which is why it's the anchor for everything else. The reverse-DNS name in parentheses is the receiving server's PTR lookup of that IP — useful, but only as strong as the sender's DNS hygiene.
To trace origin: find the bottom-most Received header that your own infrastructure added. Anything below *that* was inserted by servers you don't control, and can be forged wholesale. Spammers love to pad the bottom of the chain with fake Received lines to make a message look like it bounced through reputable hosts. The rule: trust the chain only from the first server you actually run, upward. Everything below the trust boundary is storytelling.
Return-Path, From, and the envelope vs. header split
Email has two different "from" addresses and conflating them is the root of most confusion.
The envelope sender (aka MAIL FROM, the bounce address) is set during the SMTP conversation and is where delivery failures go. The final server records it as Return-Path:.
The header From is the From: you see in the client. It's just text in the message body's header section and has no inherent connection to the envelope.
A message can legitimately have Return-Path: <[email protected]> and From: [email protected] — that's normal for bulk senders. But it's also exactly the seam spoofers exploit: they set a From: you trust and an envelope sender they control. SPF, as we'll see, only checks the envelope, which is why SPF alone never proves the visible From is genuine.
Message-ID: is a globally unique identifier assigned by the originating server, usually <random-string@sending-domain>. Two tells: the domain in the Message-ID should generally match the sending infrastructure, and a missing or malformed Message-ID is a mild red flag — most legitimate mailers generate a clean one, while some spam tooling forgets to or reuses them.
Authentication-Results: the receiving server's verdict
The most information-dense single header is Authentication-Results, written by *your* receiving server after it evaluated the message. Because your server wrote it, you can trust it (assuming you trust your server). It summarizes three checks:
SPF checks whether the observed connecting IP is authorized to send for the *envelope* domain (smtp.mailfrom). spf=pass means the IP is on that domain's published SPF record. It says nothing about the visible From.
DKIM checks a cryptographic signature. The sender signs selected headers and the body with a private key; you verify with a public key in their DNS (selector._domainkey.domain). dkim=pass [email protected] means the signature is valid *and* was made by a key belonging to store.com. DKIM survives forwarding better than SPF because it doesn't care about the connecting IP.
DMARC ties the two together with *alignment*. It passes if SPF or DKIM passes and the domain that passed lines up with the visible From: domain. This is the check that actually protects the address the human reads. dmarc=pass with header.from=store.com means the From you see is backed by a passing, aligned authentication result.
The combination to internalize: spf=pass on its own can be a spoofer's domain passing for itself. `dmarc=pass` is the one that matters because it forces alignment with the visible From.
An annotated real-world header, top to bottom
Here's a lightly sanitized header from a legitimate transactional email, annotated. Remember: newest at top.
Delivery-date: Sat, 04 Jul 2026 09:14:52 +0000
Return-Path: <[email protected]> # envelope sender (bounces here)
# --- HOP 3: my server received it from Store's outbound relay ---
Received: from mail.store.com (mail.store.com [198.51.100.24])
by mx.mymail.net (Postfix) with ESMTPS id 4Rk8p2
(using TLSv1.3) for <[email protected]>;
Sat, 04 Jul 2026 09:14:51 +0000 # TRUST BOUNDARY: my server, trust upward from here
# --- HOP 2: Store's relay received it from their app server ---
Received: from app-07.store.internal (app-07.store.internal [10.0.3.7])
by mail.store.com (Postfix) with ESMTP id 3Lm9q
Sat, 04 Jul 2026 09:14:50 +0000 # internal IP - fine, it's inside Store's network
# --- HOP 1: origin, the app that generated the mail ---
Received: from localhost (localhost [127.0.0.1])
by app-07.store.internal (SubmissionAgent) with ESMTP
Sat, 04 Jul 2026 09:14:50 +0000 # generated locally on the app host
Authentication-Results: mx.mymail.net;
spf=pass smtp.mailfrom=mail.store.com;
dkim=pass [email protected] header.s=s1;
dmarc=pass header.from=store.com # aligned - the visible From is genuine
DKIM-Signature: v=1; a=rsa-sha256; d=store.com; s=s1;
h=from:to:subject:date; bh=...; b=...
Message-ID: <[email protected]> # domain matches sender infra
From: "Store Receipts" <[email protected]> # the address the human sees
To: [email protected]
Subject: Your order #10482 has shipped
Date: Sat, 04 Jul 2026 09:14:50 +0000
Reading it: the connecting IP my server observed was 198.51.100.24, and its PTR resolved to mail.store.com, consistent with the envelope domain. SPF passed for that domain, DKIM passed with a signature from store.com, and DMARC confirms store.com aligns with the visible From:. The Received chain below my trust boundary shows a clean internal path. Nothing contradicts anything. This is what a genuine message looks like.
What spoofed and forged headers look like
Now the failure modes. Here are the patterns that make me distrust a message.
The From doesn't match the authentication. The classic phish:
SPF and DKIM both *pass* — but for sketchy-mailer.ru, not for paypal.com. DMARC fails because the visible From (paypal.com) doesn't align with anything that authenticated. This is the tell: passing SPF/DKIM means nothing if they pass for the wrong domain. Always check *what domain* passed, not just that something did.
Fabricated Received lines at the bottom. Spammers inject fake hops to fake a reputable path:
Received: from mx.mymail.net (...) by mx.mymail.net ... # real, my server
Received: from mail.google.com (spammer-vps.example [203.0.113.99]) ... # LIE
That second line claims to be from mail.google.com, but the observed IP 203.0.113.99 is a random VPS and the PTR doesn't back the HELO name. Google's real outbound IPs are published and this isn't one. Any hop below your trust boundary where the HELO name, PTR, and IP don't corroborate each other is suspect.
Mismatched timestamps. The chain should march monotonically backward in time as you go down. If an upper (later) hop is timestamped *before* a lower (earlier) one by more than clock-skew seconds, someone forged a line — or, more benignly, a relay has a badly set clock. Multi-hour jumps are the interesting ones.
Reply-To pointing somewhere unexpected. Not forgery per se, but From: [email protected] with Reply-To: [email protected] means your reply goes to a Gmail account, not the bank. Legitimate for a freelancer, alarming for a bank.
Finding the true origin IP
Once you've located the bottom-most trustworthy Received line, the observed IP in it is your origin — or the closest you can get. Then corroborate:
1. Reverse-DNS (PTR):dig -x 198.51.100.24. Does it resolve, and does the name fit the claimed sender? 2. Forward-confirm: does that PTR name resolve *back* to the same IP? A matching forward-confirmed reverse DNS (FCrDNS) is a good sign; a mismatch is a small red flag. 3. WHOIS / geolocation:whois 198.51.100.24 tells you the netblock owner and country. A "Microsoft account" email originating from a residential ISP block in an unexpected country is worth a second look. 4. Blocklist check: query the IP against a DNSBL to see if it's a known spam source.
A note on privacy while you're at it: this cuts both ways. The reason origin IPs and infrastructure leak into headers is exactly why using a disposable address for one-off signups is sensible — it keeps your primary inbox out of the correlation graph that these same headers make possible. A service like EvilMail exists for precisely that throwaway-address use case. Every message you receive is a small disclosure; headers are where those disclosures accumulate.
A quick reading checklist
When a message lands and you need a verdict fast:
Read Receivedbottom-up; find your trust boundary (first server you control).
Grab the observed IP at that boundary; ignore HELO names as identity.
Check Authentication-Results: is it dmarc=pass, and does header.from match the visible From?
Confirm what domain SPF and DKIM passed for — not merely that they passed.
Compare Return-Path (envelope) against From: (visible); a mismatch is normal for bulk mail, suspicious for a "personal" note from a bank.
Sanity-check timestamps for monotonic order down the chain.
Corroborate the origin IP with PTR, WHOIS, and a blocklist lookup.
Headers don't lie about the parts your own infrastructure wrote — that's the whole trick. Learn where your trust boundary sits, read down from the truth and up toward it, and the message tells you where it really came from whether or not it wanted to.