Skip to content
Guide

How to trace a message through its own headers

Headers are the only record a message keeps of its own journey. Read in the right order they give the path it took, the delay at each hop, the protocol and encryption used, and what every receiver concluded about who sent it.

Updated 5 August 2026 8 minute read

Headers are stacked, newest first

Every server that handles a message adds its trace headers to the top, and never reorders what is already there. The result is a stack in reverse chronological order: the topmost Received header was added by the last server to touch the message, and the bottom one by the first.

So a chain is read from the bottom upwards to follow the message forwards in time. That single fact resolves most confusion about header analysis, including the common misreading in which the recipient's own mail platform is mistaken for the origin of a message.

Email headers fall into two groups, and only the trace headers are ordered in this way. The message's own headers — From, To, Subject, Date, Message-ID — were written once by the author's client and are not touched afterwards, which is exactly why DKIM can sign them.

Anatomy of a Received line

A Received line is defined by RFC 5321 and follows a fixed grammar, however unreadable it appears at first. Each clause answers one question, and the timestamp after the semicolon is when that server finished accepting the message.

The from clause holds two things: the name the sending host announced in EHLO, then in brackets the name and address the receiver observed. When those disagree, the receiver is telling you so deliberately. The by clause names the receiving host, with names the protocol used — ESMTPS for an encrypted session, ESMTPSA for an authenticated one, plain SMTP or ESMTP for neither — and id gives the receiving server's own queue identifier, which is what a postmaster needs to find the message in a log.

One hop: who connected, what they announced, which host accepted it, over what protocol and encryption, for whom, and when.
Received: from mail.example.com (mail.example.com [192.0.2.25])
    by mx1.example.net (Postfix) with ESMTPS id 6C2A41F0B3
    (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384)
    for <[email protected]>; Wed, 05 Aug 2026 09:14:22 +0000 (UTC)

Finding the delay

The delay at a hop is the difference between its timestamp and the timestamp of the hop below it. Working up the chain and subtracting gives a per-hop profile, and in almost every slow delivery a single hop holds nearly all of the elapsed time.

A gap of minutes at one hop usually means queueing: greylisting, which asks the sender to retry later and adds whatever the sender's retry interval is, or rate limiting at a receiver that has decided your volume is more than it wants at once. A gap of seconds at a hop that also mentions a scanning product is content inspection. A gap of hours is a retry after a temporary failure.

Two cautions. Clocks are not synchronised across independent servers, so small negative or implausible intervals are skew rather than time travel, and the timestamp records when a server finished accepting the message, so a large message on a slow link inflates the hop that received it.

Authentication-Results

The Authentication-Results header, defined by RFC 8601, is where a receiver records what its checks concluded. It begins with the authserv-id — the name of the system that performed the checks — followed by one clause per method.

Trust only the topmost instance from the system that delivered the message. The header is ordinary text and anything below can have been written by an earlier hop, or by the sender. A receiving platform that adds its own removes or renames those it does not vouch for, and yours is the one nearest the top.

Read the identity on each clause rather than the result alone. spf=pass with smtp.mailfrom naming a bounce domain, dkim=pass with header.d naming a sending platform, and dmarc=fail with header.from naming your domain is a complete and coherent story: both mechanisms passed, neither aligned.

Two passing mechanisms and a DMARC failure. Every identity names example.org except the one the recipient sees.
Authentication-Results: mx1.example.net;
    dkim=pass header.d=example.org header.s=mail header.b=Qp1RtY9c;
    spf=pass (mx1.example.net: domain of [email protected] designates
        192.0.2.25 as permitted sender) smtp.mailfrom=example.org;
    dmarc=fail (p=REJECT sp=REJECT dis=REJECT) header.from=example.com

Return-Path, Message-ID and the identities they carry

Return-Path is added by the last server, and records the envelope sender from MAIL FROM. It is where bounces go, and it is the identity SPF was evaluated against — which makes it the fastest way to see why an SPF result refers to a domain you did not expect.

Message-ID is a globally unique identifier written by the originating client, in angle brackets with a domain on the right. It threads replies through In-Reply-To and References, and it is the one value that identifies a specific message across every log it passes through, which makes it the right thing to quote in a support request.

A missing or malformed Message-ID, or one whose right-hand side is unrelated to the sending domain, is a small negative signal to content filters. It is generated by whatever composed the message, so an application sending mail through a library that omits it is worth fixing at the source.

ARC sets

ARC, defined by RFC 8617, exists for intermediaries that must modify a message and thereby break its DKIM signature. Rather than leave the final receiver with an unexplained failure, the intermediary records the authentication results it observed and seals that record cryptographically.

Each participating hop adds a set of three headers sharing an instance number: ARC-Authentication-Results, holding what that hop saw; ARC-Message-Signature, signing the message as that hop is passing it on; and ARC-Seal, signing the chain so far. The seal's cv value reports the chain's validity as that hop found it — none for the first, pass for a chain that verified, fail for one that did not.

An intact chain lets a final receiver see that a message authenticated correctly before a list rewrote it, and decide to deliver a message that fails DMARC on its own. It is an input to that decision, not an override: the receiver still has to trust the sealing intermediary.

The first ARC set, added by a mailing list: cv=none because no chain existed yet, and a record that the message authenticated before the list touched it.
ARC-Seal: i=1; a=rsa-sha256; t=1785974400; cv=none;
    d=lists.example.org; s=arc; b=Vb2Nq...
ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed;
    d=lists.example.org; s=arc; h=from:subject:date; bh=...; b=...
ARC-Authentication-Results: i=1; lists.example.org;
    spf=pass smtp.mailfrom=example.com; dkim=pass header.d=example.com;
    dmarc=pass header.from=example.com

When the message was rejected rather than delayed

A message that never arrived leaves its evidence in a bounce rather than in the recipient's headers. A delivery status notification, defined by RFC 3464, is a structured report that carries the original headers alongside the reason, and the reason is worth reading precisely.

Two codes appear. The SMTP reply code gives the class — 4xx temporary, 5xx permanent — and the enhanced status code, defined by RFC 3463, gives the detail: 5.1.1 for an unknown recipient, 5.7.1 for a policy refusal, 4.7.0 for a temporary security-related deferral. A 5.7.x code with a DMARC mention names the exact record that caused the rejection, which is usually the entire diagnosis.

Keep the original headers from a bounce. They contain the Received chain up to the point of failure and the Message-ID, which are the two things a receiving postmaster will ask for.

Check this on your own domain. The spf record checker reads the live records and reports what a receiver would see.

Open the spf record checker

Common questions

In which order should Received headers be read?
From the bottom upwards. Each server adds its trace header to the top of the message without reordering the rest, so the lowest Received line describes the first hop and the topmost describes the last. Reading downwards reverses the journey and commonly leads to mistaking the recipient's own platform for the message's origin.
How do I find which hop delayed a message?
Subtract each Received timestamp from the one directly above it. Almost every slow delivery has one hop holding nearly all the elapsed time — typically greylisting or rate limiting at a receiver. Allow for unsynchronised clocks between independent servers, which produce small implausible intervals that are skew rather than real delay.
Can email headers be forged?
Anything below the topmost trace header can be. A sender may insert fabricated Received and Authentication-Results lines before transmitting, and every hop leaves them in place. Only the headers added by systems you control or trust — starting with the one that delivered to the mailbox — are evidence; the rest are claims.
What is the difference between Return-Path and From?
Return-Path records the envelope sender from MAIL FROM, is added by the delivering server, receives bounces and is the identity SPF checked. From is written by the author's client and is what a mail application displays. They frequently differ for legitimate reasons, and DMARC exists to require that a passing mechanism refer to the From domain.

Keep reading

Related checks, definitions and guides.