Skip to content

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.

Published on Updated on 12 min read

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 from clause 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 ESMTPA or (Authenticated sender: …) indicate the sender logged in. That ties the message to a credential, which matters in account-takeover cases.

Decoding a hop, field by field

A Received: header is free-form text with a loose grammar (RFC 5321 §4.4): a sequence of from, by, via, with, id and for clauses, parenthesised comments that MTAs fill with whatever they like, and a date after the final semicolon. Take this hop, as Postfix writes it:

Received: from mail.acme-paymemts.test (mail.acme-paymemts.test [203.0.113.44])
        (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits))
        by mx1.example.com (Postfix) with ESMTPS id 4Tq9Zk1
        for <finance@example.com>; Mon, 04 Mar 2024 08:12:49 +0000
PartValue hereWhat it tells you
from wordmail.acme-paymemts.testHELO/EHLO name the client announced. Free text: a laptop may announce DESKTOP-7H2K, a spammer anything.
from comment, namemail.acme-paymemts.testReverse-DNS name the receiving server looked up. unknown means no PTR record.
from comment, IP203.0.113.44The address the receiver actually saw on the TCP connection. The most useful field in the header.
bymx1.example.com (Postfix)The server that wrote this line, often with its software in the comment.
withESMTPSProtocol, with RFC 3848 suffixes: S = TLS, A = authenticated, SA = both. LMTP is the final hand-off into the mailbox store.
TLS commentTLSv1.3Postfix writes (using TLSv1.3 …), Exchange (version=TLS1_2, cipher=…), Gmail (Google Transport Security). No TLS on an internet-facing hop is worth noting.
id4Tq9Zk1Queue ID on the receiving server: the key to find this message in that server's logs.
forfinance@example.comEnvelope recipient (RCPT TO), which may differ from the visible To: for Bcc or aliases.
date08:12:49 +0000When this server accepted the message, on its clock.

Decode every hop the same way, then line them up origin-first. For an invoice-fraud message relayed through a lookalike domain, that gives:

Date: header       2024-03-04 08:12:44 UTC   (written by the sender's mail client)
hop 1  DESKTOP-7H2K [192.168.8.23, private]  → mail.acme-paymemts.test   esmtpsa, TLS, authenticated   +2 s
hop 2  mail.acme-paymemts.test [203.0.113.44] → mx1.example.com          ESMTPS, TLSv1.3               +3 s   ← first internet-routable client IP
hop 3  mx1.example.com [10.20.0.5]            → mbox.example.com         LMTP (internal delivery)      +2 s

Read this way, the story is plain. Someone logged in to the lookalike domain's relay from a workstation called DESKTOP-7H2K on a private network, and that relay connected to your MX from 203.0.113.44. Hop 1 is on the sender's side of the trust boundary, so its details (including the workstation name) are evidence of what the sender's server wrote, not proof. Hop 2 was written by your MX, so 203.0.113.44 is the IP you can take to the provider and to threat-intel lookups.

Two measurements come for free once hops are ordered. The per-hop delay shows queueing, greylisting or skew. The gap between Date: and the first hop shows how long the sender's client held the message, or whether Date: was forged.

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-To and References carry the IDs of earlier messages in the thread. A reply whose In-Reply-To points 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.

HeaderSet byWhat it represents
Return-PathFinal delivering MTA, from the SMTP MAIL FROMEnvelope sender, where bounces go. This is the domain SPF evaluates.
FromSender's clientThe author shown to the user. This is the domain DMARC protects.
Reply-ToSender's clientWhere 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 the From: domain.
  • DKIM verifies a cryptographic signature. header.d= is the signing domain and header.s= is the selector. A pass proves that d= domain signed the covered headers and body, not that it matches From:.
  • DMARC requires SPF or DKIM to pass and align with the From: domain. In the example, SPF and DKIM both pass for mailer.example.net, but neither aligns with acme-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 trusted Received: 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/alternative holds the same content in several formats. The plain and HTML parts can differ, and attackers occasionally hide text in one version only.
  • multipart/related bundles HTML with inline images referenced by cid:.
  • message/rfc822 is an entire email attached inside another, which is common in forwarded-as-attachment phishing reports.
  • Content-Transfer-Encoding is usually base64 for binary parts and quoted-printable for mostly-ASCII text (=C3=A9 is 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 into filename*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

FormatWhat it isNotes
EMLOne RFC 5322 message as raw textThe best evidence format, since headers and bytes are intact.
MBOXMany messages concatenated, each starting with a From separator lineBody lines starting with From are escaped as >From . Used by Thunderbird and Gmail Takeout.
EMLXApple Mail's per-message fileFirst line is a byte count, then the RFC 822 message, then an Apple property list.
MSGOutlook item in an OLE compound file with MAPI propertiesReceived internet mail usually keeps the original headers in the transport headers property. Sent items and internal mail may not have them.
PST / OSTOutlook data files holding whole mailboxes ([MS-PST])A PST is a portable store (archives, POP/IMAP accounts, exports); an OST is the local cache of an Exchange / Microsoft 365 mailbox. Items keep their transport headers when they were received from the internet.

Where they live on Windows: %LOCALAPPDATA%\Microsoft\Outlook\*.ost for cached mailboxes, and Documents\Outlook Files\*.pst for data files (older Outlook versions also kept PSTs under %LOCALAPPDATA%). Quit Outlook before copying: an open data file is locked. Classic Outlook exports a mailbox with File → Open & Export → Import/Export → Export to a file → Outlook Data File (.pst). New Outlook for Windows keeps no OST for Microsoft 365 accounts and has limited PST support, so for a whole mailbox use classic Outlook or a Microsoft Purview eDiscovery PST export.

An OST only holds what was synchronized: items outside the sync window, or never downloaded, are missing. Hash the file before anything else, and compare item counts per folder with what Outlook or your review platform reports. Deleted Items is an ordinary folder, and Exchange's Recoverable Items (soft-deleted mail) often appears in OSTs; items purged from the file are only recoverable by carving its free blocks.

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, Outlook .msg and whole Outlook .pst / .ost mailboxes 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:

  • SMTP route: every Received: header is decoded into a hop: announced HELO name, reverse-DNS name, IP and its scope (public, private, loopback, CGNAT, documentation range), receiving server and software, protocol, TLS version, authenticated submission, queue ID, envelope recipient and timestamp. When the client used a bare IP literal but announced a name in helo=, the name is shown. It is often the sender's workstation name. Hops are listed origin-first between the Date: header and final delivery, each with its delay since the previous one: negative delays are red, gaps over five minutes amber. The first internet-routable client IP is marked as the likely sending host. Total transit time and the Date:-to-first-hop gap are computed for you, and the raw header stays one click away.
  • Checks: SPF, DKIM and DMARC verdicts are read from every Authentication-Results and ARC-Authentication-Results header (and Received-SPF when that is all there is), de-duplicated, and shown as pass/fail badges. The route view flags Reply-To and Return-Path domains that differ from From, DKIM signatures from a domain other than From, authentication failures, internet-facing hops without TLS, hops timestamped before the previous one, and a suspicious Date: gap. Each flag names the domains or hops involved.
  • Headers: the detail panel also lists Message-ID, In-Reply-To, References, Return-Path, Reply-To, X-Originating-IP, X-Sender-IP, X-Mailer, User-Agent, DKIM-Signature (signature value truncated), X-MS-Exchange-Organization-AuthAs, List-Id and, for mbox, X-Gmail-Labels. For Outlook .msg, .pst and .ost, all of this comes from the transport headers property, so it is available when the item kept its internet headers.
  • 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. MIME attachments are not extracted or hashed, so do that in your forensic suite; PST and OST attachments are SHA-256 hashed and can be downloaded one by one. The text of attached message/rfc822 parts 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, PST and OST, it is the client submit time, falling back to delivery time and then creation time. The route view puts that Date: next to the server timestamps, so a backdated or forward-dated message stands out.
  • Threads: messages are grouped by subject with Re:/Fwd:-style prefixes stripped (several languages).
  • Limits: verdicts are displayed as recorded; SPF, DKIM and DMARC are not re-verified. The tool does not know where your trust boundary is, so it cannot tell which Authentication-Results authserv-id is yours: check that the badge comes from your own boundary server. No DNS or WHOIS lookups are made (everything stays in the browser), so enrich the origin IP in your own tooling. PST and OST files are read in the browser, piece by piece, by a WebAssembly engine (Unicode and ANSI PST, Outlook 2013+ OSTs with compressed blocks); items purged from the file's free space are not carved. Header fields are not covered by the search box. The HTML report carries a one-line route per email, and the JSON export has the full decoded route; the CSV export has a fixed column set without headers.

Open the analyzer and drop in an .eml or mbox to see the SMTP route, or click Try a sample and open the "Invoice KS-40817 overdue - action required" email to see a three-hop route from a lookalike vendor domain, with the failed SPF/DMARC and Reply-To mismatch flagged. The sample also holds the victim's Outlook .pst: open Mailbox folders to browse it, including the request the intruders sent from her mailbox and then moved to Deleted Items. Related guides: Microsoft Teams forensics, Slack export forensics, and what is messaging forensics.

Related articles

Acquire Teams chats via Purview eDiscovery, the Microsoft Graph chatMessage API or a Teams (free) export, and know which fields, edits and deletions matter.
What a Slack workspace export contains, how to read ts, thread_ts, edits, tombstones and message markup, what exports leave out, and how to verify integrity.
Messaging forensics explained: acquisition options, a normalized message model, the timestamp zoo, evidence hashing and the pitfalls that break timelines.