Email Header Forensics: Received, SPF, DKIM and MIME
Read Received chains bottom-up, compare Return-Path, From and Reply-To, interpret SPF/DKIM/DMARC results and decode MIME across EML, MBOX, MSG and PST.
Most email investigations come down to two questions: where did this message really come from, and when. The answer is almost never in the visible From: line. It is spread across a dozen header fields. Some were written by servers you can trust, and some were typed by the sender. This guide covers how to read them, which ones carry weight, and how the container format changes what you can recover.
Reading the Received chain bottom-up
Every mail transfer agent (MTA) that accepts a message adds a Received: header at the top of the header block. The chain therefore reads in reverse. The lowest Received: line is the earliest hop, and the topmost one was added by the server that delivered to the mailbox.
Received: from mx.example.com (mx.example.com [198.51.100.25])
by mail.corp.example (Postfix) with ESMTPS id 4XyZ1
for <ap@corp.example>; Tue, 05 Mar 2024 09:14:07 +0000
Received: from [10.0.0.12] (unknown [203.0.113.45])
by mx.example.com with ESMTPSA id abc123
for <ap@corp.example>; Tue, 05 Mar 2024 09:14:05 +0000
Read it as: a client at 203.0.113.45 authenticated (ESMTPSA) to mx.example.com, which relayed to mail.corp.example two seconds later.
The rule that matters is trust boundary. Only the hops written by infrastructure you control, or by a provider whose logs you can subpoena, are reliable. Anything below the first hop your organization wrote was supplied by the sending side and can be fabricated. Attackers sometimes pad messages with fake lower Received: lines to suggest a different origin.
Things to check on each hop:
- The
fromclause has two parts: the HELO/EHLO name the client claimed, and the reverse-DNS name plus IP in parentheses that the receiving server observed. The observed IP is the useful part. - Timestamps should increase as you move up the chain. Large gaps point to queueing, greylisting or a relay outage. A later hop showing an earlier time than the hop below it points to clock skew or fabrication.
with ESMTPSA,with ESMTPAor(Authenticated sender: …)indicate the sender logged in. That ties the message to a credential, which matters in account-takeover cases.
Message-ID, In-Reply-To and References
Message-ID is normally generated by the sender's mail client or first submission server, for example <a1b2c3d4@mail.example.com>. It is not authenticated, but it is still valuable:
- The right-hand side often reveals the real sending platform, even when the
From:domain says otherwise. - The same ID appearing in two mailboxes proves you are looking at the same message, not a re-send.
In-Reply-ToandReferencescarry the IDs of earlier messages in the thread. A reply whoseIn-Reply-Topoints at a message nobody in the organization ever received is a classic sign of a fabricated thread in business email compromise (BEC).
Return-Path vs From vs Reply-To
These three fields hold different identities, and comparing them is the core of phishing and BEC triage.
| Header | Set by | What it represents |
|---|---|---|
Return-Path | Final delivering MTA, from the SMTP MAIL FROM | Envelope sender, where bounces go. This is the domain SPF evaluates. |
From | Sender's client | The author shown to the user. This is the domain DMARC protects. |
Reply-To | Sender's client | Where replies go. Nothing authenticates it. |
A typical invoice-fraud pattern:
Return-Path: <bounce-7731@mailer.example.net>
From: "Claire Martin" <claire@acme-payments.test>
Reply-To: <claire@acme-paymemts.test>
The Reply-To domain is a lookalike (paymemts vs payments). The victim sees a legitimate From:, hits reply, and the conversation moves to a domain the attacker controls. Compare domains character by character. Watch for transposed letters, rn vs m, extra hyphens, different TLDs, and internationalized domains that render as look-alike Unicode.
X-Originating-IP and other vendor headers
Some webmail services and mail platforms have historically recorded the client IP in X-Originating-IP or similar headers (X-Sender-IP). Coverage varies by provider and has changed over time, since many large providers stopped exposing client IPs for privacy reasons. When present, it can identify the sender's network. When absent, that tells you nothing. Microsoft 365 adds a family of X-MS-Exchange-* headers, and X-MS-Exchange-Organization-AuthAs indicates whether the submission was anonymous, internal or authenticated from the tenant's point of view.
Authentication-Results, SPF, DKIM and DMARC
The receiving server records its authentication verdicts in Authentication-Results (RFC 8601):
Authentication-Results: mail.corp.example;
spf=pass smtp.mailfrom=mailer.example.net;
dkim=pass header.d=mailer.example.net header.s=s1;
dmarc=fail (p=reject) header.from=acme-payments.test
How to read it:
- SPF checks whether the connecting IP is authorized to send for the envelope (
Return-Path) domain. A pass says nothing about theFrom:domain. - DKIM verifies a cryptographic signature.
header.d=is the signing domain andheader.s=is the selector. A pass proves thatd=domain signed the covered headers and body, not that it matchesFrom:. - DMARC requires SPF or DKIM to pass and align with the
From:domain. In the example, SPF and DKIM both pass formailer.example.net, but neither aligns withacme-payments.test, so DMARC fails.
Only trust an Authentication-Results header whose authserv-id (the first token, here mail.corp.example) is your own boundary server. A sender can insert a fake one lower in the block. Received-SPF is an older, SPF-only equivalent. Forwarded mail may also carry ARC headers (ARC-Authentication-Results, ARC-Seal) that preserve verdicts from earlier hops.
You can re-verify DKIM later only if the signing key is still published in DNS and the message bytes were not altered. Re-save, re-encode or convert a message and the body hash can break.
Date header vs Received timestamps
Date: is written by the sender's client from the sender's clock. It can be wrong or deliberately backdated. The Received: timestamps written by your own servers are the reliable delivery times. Compare them:
- A
Date:a few seconds or minutes before your first trustedReceived:is normal. - A
Date:hours or days off suggests a skewed clock, a message that sat in a queue or outbox, or manipulation. - Normalize everything to UTC before comparing. The offset (
+0000,-0500) is part of each timestamp.
MIME structure
The body of a modern email is a MIME tree. Common shapes:
multipart/mixed
├── multipart/alternative
│ ├── text/plain; charset=utf-8 (quoted-printable)
│ └── text/html; charset=utf-8 (quoted-printable)
└── application/pdf; name="invoice.pdf" (base64)
multipart/alternativeholds the same content in several formats. The plain and HTML parts can differ, and attackers occasionally hide text in one version only.multipart/relatedbundles HTML with inline images referenced bycid:.message/rfc822is an entire email attached inside another, which is common in forwarded-as-attachment phishing reports.- Content-Transfer-Encoding is usually
base64for binary parts andquoted-printablefor mostly-ASCII text (=C3=A9is UTF-8 "é", a trailing=is a soft line break). - RFC 2047 encoded words encode non-ASCII headers:
Subject: =?UTF-8?B?RmFjdHVyZSDDqWNodWU=?=(B = base64, Q = quoted-printable variant). - RFC 2231 parameters encode attachment names:
filename*=UTF-8''facture%20%C3%A9chue.pdf, sometimes split intofilename*0*=,filename*1*=continuations.
Always look at the decoded filename and the declared MIME type. invoice.pdf declared as application/octet-stream, or a name with a right-to-left override character, deserves a closer look.
Container formats
| Format | What it is | Notes |
|---|---|---|
| EML | One RFC 5322 message as raw text | The best evidence format, since headers and bytes are intact. |
| MBOX | Many messages concatenated, each starting with a From separator line | Body lines starting with From are escaped as >From . Used by Thunderbird and Gmail Takeout. |
| EMLX | Apple Mail's per-message file | First line is a byte count, then the RFC 822 message, then an Apple property list. |
| MSG | Outlook item in an OLE compound file with MAPI properties | Received internet mail usually keeps the original headers in the transport headers property. Sent items and internal mail may not have them. |
| PST / OST | Outlook data files holding whole mailboxes | Must be converted before per-message analysis. |
For PST, readpst from the libpst project is the common open-source route:
# one mbox per folder, keep deleted items, recurse the folder tree
readpst -D -r -o ./out mailbox.pst
# or one .eml file per message
readpst -D -e -o ./out mailbox.pst
Hash the PST before converting, and check message counts per folder against what Outlook or your review platform reports. OST support in libpst exists but is less predictable, so treat OST conversion output with extra verification.
Gmail Takeout
Google Takeout exports mail as mbox, typically Takeout/Mail/All mail Including Spam and Trash.mbox. Each message carries an X-Gmail-Labels header, such as X-Gmail-Labels: Inbox,Important,Opened, which records the Gmail labels, including Sent, Trash and Spam. There is also an X-GM-THRID header with Gmail's thread ID. Labels are the only way to reconstruct the folder view from a single flat mbox.
Doing this in MessagingForensics
MessagingForensics parses .eml, .emlx, .mbox and Outlook .msg entirely in your browser. Every file you add is SHA-256 hashed before parsing, and ZIP archives are expanded in memory. What it does with email:
- Headers: the message detail panel shows
Message-ID,In-Reply-To,References,Return-Path,Reply-To,X-Originating-IP,X-Sender-IP,X-Mailer,User-Agent,Authentication-Results,Received-SPF,DKIM-Signature(signature value truncated),X-MS-Exchange-Organization-AuthAs,List-Id, and for mbox,X-Gmail-Labels. It also shows the fullReceived:chain, the hop count, and every bracketed IP found in it. - MIME: it walks multipart trees, decodes base64 and quoted-printable, honors declared charsets, and decodes RFC 2047 headers and RFC 2231 filenames. Attachments are listed with name, type and decoded size. They are not extracted or hashed, so do that in your forensic suite. The text of attached
message/rfc822parts is folded into the parent message's body. - Time: for EML, EMLX and mbox, the timeline time is the
Date:header, converted to UTC. For MSG, it is the client submit time, falling back to delivery time and then creation time. Compare against theReceived:chain yourself. - Threads: messages are grouped by subject with
Re:/Fwd:-style prefixes stripped (several languages). - Limits: it records authentication verdicts but does not re-verify SPF, DKIM or DMARC. It keeps only the first (topmost)
Authentication-Resultsheader. PST and OST files are rejected with a prompt to convert them with readpst first. Header fields are not covered by the search box or the indicators tab. Export to JSON to keep every header alongside each message, since the CSV export has a fixed column set without headers.
Open the analyzer and drop in an .eml or mbox to see the header view. Related guides: Microsoft Teams forensics, Slack export forensics, and what is messaging forensics.