Reading an email header to find where the delay happened
"The email took four hours to arrive." The header block holds the answer, and it is usually one hop that accounts for nearly all of it. The difficulty is that the block is long, repetitive and printed in an order that feels backwards.
Read it from the bottom up
Every server that handles a message prepends its own
Received: line to the top of the header block. Nothing rewrites what
is already there.
So the bottom Received: line is the first hop, written by
the sender's own server, and the top one is the last hop, written by
the recipient's. To follow the message in the order it actually travelled, read
from the bottom upwards.
This also tells you something about trust. A server can only vouch for what it
saw. Lines near the bottom were written by machines further from you and closer
to a potential forger — and a spammer can invent as many
Received: lines as they like before handing the message over. Only
the lines added by servers you control, at the top, are reliable.
What one line contains
Received: from mail.sender.example (mail.sender.example [198.51.100.25])
by mx.recipient.example (Postfix) with ESMTPS id 4Wq8Kt2Yz1z
for <[email protected]>; Sat, 6 Sep 2026 09:14:02 +0000 (UTC)
- from — the name the sending server announced, followed in brackets by the name and address the receiver actually observed. When these disagree, the announced name was simply a claim.
- by — the server writing this line.
- with — the protocol.
ESMTPSmeans the hop was encrypted; plainESMTPorSMTPmeans it was not. - the timestamp — when this server received it, with an offset.
Finding the delay
Take the timestamp from each Received: line and compare each with
the one below it. The difference is how long the message sat at that hop.
Two traps make this harder than subtraction:
- Time zones. Each server writes its own offset. Convert everything to UTC before comparing, or a hop across zones will look like a multi-hour delay that never happened.
- Clock skew. Servers with a drifting clock can produce a negative gap, where a message appears to arrive before it was sent. A small negative number is skew, not time travel. A large one means a clock is badly wrong and none of the timings around it can be trusted.
In practice one hop usually holds nearly all of the elapsed time. A multi-hour gap is almost always a receiving server that deferred the message — greylisting, a full queue, rate limiting, or a spam filter holding it for analysis. A gap of a few seconds spread evenly across hops is normal.
The authentication line
The receiving server records its verdict in one header:
Authentication-Results: mx.recipient.example; spf=pass smtp.mailfrom=sender.example; dkim=pass header.d=sender.example; dmarc=pass (p=reject dis=none) header.from=sender.example
Read the identifier attached to each result, not just the pass or fail.
spf=pass tells you the envelope domain was authorised;
smtp.mailfrom names which domain that was. dkim=pass with
header.d names the signing domain. DMARC then checks those against
header.from.
When SPF and DKIM both pass but DMARC fails, the domains did not align — a different problem with a different fix, covered in why SPF passes but DMARC still fails.
This header is written by the receiving server. If a message shows an
Authentication-Results line from a server that is not yours, it proves
nothing: anyone can add one.
Other headers worth a look
Return-Path— the envelope sender, where bounces go. Frequently different fromFrom:, and legitimately so.Message-ID— unique per message. The domain in it often reveals the platform that generated the mail.Reply-To— a classic phishing tell when it points somewhere unrelated toFrom:. The display name says your finance director; the reply goes to a free mail account.X-Spam-*orX-Forefront-*— filter scores. Non-standard and vendor-specific, but they often state outright why a message was quarantined.
Where to get the raw headers
Forwarding an email does not carry its headers — you get the headers of the forward. You need the original source:
- Gmail: open the message, the three-dot menu, "Show original".
- Outlook: File → Properties, then the "Internet headers" box.
- Apple Mail: View → Message → All Headers.
- Thunderbird: View → Message Source, or Ctrl+U.
Headers contain recipient addresses, internal hostnames and internal IP addresses. Treat a header block as internal information before pasting it anywhere.
The Email Header Analyzer takes a raw header block, orders the hops chronologically, converts the timestamps and shows the delay at each one, alongside the SPF, DKIM and DMARC results.