Email & deliverability

Email headers: how to read one, and what it proves

Above the message you read there is a block of lines written by machines rather than by the person who sent it. Most of it is noise, four fields are not, and exactly one of them is a line an impostor cannot write. Here is where to find the block, which direction to read it in, and what it actually proves once you have.

  • Beginner
  • 21 min read
A blue envelope with a long grey paper tape unrolling from behind it, marked with dashes instead of words, under a grey magnifying glass

What the header block actually is

Every message is two things separated by one blank line: a block of Name: value lines, and the text you read. RFC 5322 calls the first part the header and each line in it a field, and it sets no limit on how many there may be. Your mail app shows you four or five. A message that has crossed three servers usually carries between thirty and sixty.

message source, trimmed
Return-Path: <bounces+2841@mail.example.com>
Delivered-To: signup-2026@example.org
Received: by mx.example.org (Postfix) with LMTP id 4c8f21
        for <signup-2026@example.org>; Fri, 5 Sep 2026 09:14:22 +0000 (UTC)
Received: from out-17.mail.example.com (out-17.mail.example.com [198.51.100.17])
        by mx.example.org (Postfix) with ESMTPS id 9a31b0
        for <signup-2026@example.org>; Fri, 5 Sep 2026 09:14:21 +0000 (UTC)
Authentication-Results: mx.example.org;
        spf=pass smtp.mailfrom=mail.example.com;
        dkim=pass header.d=example.com;
        dmarc=pass header.from=example.com
From: "Example Support" <support@example.com>
To: signup-2026@example.org
Subject: Confirm your email address
Date: Fri, 5 Sep 2026 09:14:19 +0000
Message-ID: <20260905091419.9a31b0@mail.example.com>
MIME-Version: 1.0
Content-Type: multipart/alternative; boundary="b1_4c8f21"

Read top to bottom it looks like machine noise, and most of it is. Four fields carry nearly all of the information, and they are the four this guide is about: Received, From, Return-Path and Authentication-Results.

Where the full header is hiding

Nothing below needs a tool, an extension or an account. Every mail client keeps the raw message behind a menu item, and the word to look for is always some form of source, original or raw.

Where you read your mailWhat to openWhat you get
Gmail, in a browserThe three-dot menu on an open message → Show originalThe whole block, above a summary panel that has already graded SPF, DKIM and DMARC for you
Outlook.com, in a browserThe three-dot menu → ViewView message sourceThe whole message as it arrived, as plain text
The Outlook desktop appOpen the message in its own window → FilePropertiesThe header block only, in the box at the bottom of the dialogue
Apple Mail, on a MacViewMessageRaw SourceHeader and body together, in a window of their own
Thunderbird, on any systemViewMessage SourceThe whole message, with the header block at the top
Proton Mail, in a browserThe three-dot menu on a message → View headersThe header block on its own, without the body
Yahoo Mail, in a browserMoreView raw messageThe whole message, exactly as it was delivered
A file somebody sent you, or one you exportedOpen the .eml in any text editorEverything, because a header block and a body is all an .eml is

Those menu paths are where they stood on 5 September 2026 and they move about once a year, which is why the sentence above matters more than the table: whatever the item is called this season, it will have source, original or raw in it.

The Received chain, read from the bottom

Every server that accepts the message writes one Received: line and puts it at the top of the block, above everything already there. That single habit produces the most useful fact about headers there is: the last hop is the first line you see, and the message’s journey is written down backwards.

your providerwritten last — top linea relay in the middlewritten secondthe sending serverwritten first — bottom lineyou can rely on this linethis one is only a claimread the journey this way
Three hops, three lines, in opposite orders. The line your own provider wrote sits at the top and is the one you can rely on; the bottom line is whatever the sending machine chose to say about itself.
the Received chain of the same message
Received: by mx.example.org (Postfix) with LMTP id 4c8f21
        for <signup-2026@example.org>; Fri, 5 Sep 2026 09:14:22 +0000 (UTC)
Received: from out-17.mail.example.com (out-17.mail.example.com [198.51.100.17])
        by mx.example.org (Postfix) with ESMTPS id 9a31b0
        for <signup-2026@example.org>; Fri, 5 Sep 2026 09:14:21 +0000 (UTC)
Received: from app-3.internal (app-3.internal [192.0.2.53])
        by out-17.mail.example.com (Postfix) with ESMTP id 771c4e
        for <signup-2026@example.org>; Fri, 5 Sep 2026 09:11:58 +0000 (UTC)

Each line is built from the same handful of words, and once you can name them the chain stops being wallpaper.

from
What the connecting machine called itself, followed in brackets by what it actually was — the reverse-DNS name and the IP address the receiving server saw on the connection. The name before the brackets is a claim. The address inside them was observed.
by
The server that wrote this line. It is the only party in the line you can hold responsible for any of it.
with
How the hop was made: ESMTP, ESMTPS when the connection was encrypted, LMTP for the final handover into a mailbox. A hop that says ESMTP with no S carried the message in clear.
for
The envelope recipient at that hop — which of your addresses the message was really sent to, before any forwarding rule rewrote anything. On a catch-all domain or a plus tag, this is the field that names the address that leaked.
the timestamp at the end
When that server finished accepting the message. Subtract one line’s time from the line above it and you have the delay at that hop, which is how you find out where a slow message actually sat rather than guessing.

In the sample above, the message left the application at 09:11:58 and reached the sending provider’s outbound server two minutes and twenty-three seconds later; the two hops after that took one second between them. A message that arrives an hour late almost always has one line with an hour inside it, and it is rarely the last one.

Four fields that all claim to be the sender

“Who sent this” has four answers in a header and they are allowed to disagree. Most of the time they disagree for dull reasons — a mailing list, a marketing platform, a forwarding rule you set up yourself. A mismatch is not proof of anything. Knowing which of the four you are looking at is.

From:
The address the sender wants shown, plus a display name that is free text. Nothing verifies either one. This is the field your mail app puts in the message list, and on a phone the address is usually hidden behind the name entirely.
Return-Path:
The envelope sender, written into the header by the server that accepted the message, from what was actually said during the SMTP conversation. It is where a bounce goes, and it is the address SPF is checked against — which is exactly why a message can pass SPF while carrying a From: that has nothing to do with it.
Reply-To:
Where your reply will be addressed, which does not have to be where the message came from. Ordinary senders use it for support desks and no-reply addresses. It is also the oldest trick in the business, because a careful reader checks the sender, decides it is fine, and then presses Reply.
Delivered-To: and X-Original-To:
Which of your addresses was used. On a catch-all domain or with a plus tag, this is the field that names the address you actually handed out — the one that identifies who let it go.
a display name that says one thing and an address that says another
From: "Example Support <support@example.com>" <billing@example.net>
         ^-- the display name, which is free text and is all a phone shows
                                              ^-- the address, which is not

The display name is arbitrary text, so it can contain a complete address belonging to somebody else. Every mail client in the world will show the part in quotes and hide the part in angle brackets, which is the entire attack: it costs nothing to write and it works on the one field a reader is guaranteed to look at.

SPF, DKIM and DMARC, in one line

One field is unlike every other field in the block. Authentication-Results: is written by the server that accepted the message — yours — and anything arriving under the same name is stripped or renamed before you see it. It is the only line in a header that an impostor cannot write.

the verdict your own provider recorded
Authentication-Results: mx.example.org;
        spf=pass (sender IP is 198.51.100.17) smtp.mailfrom=mail.example.com;
        dkim=pass header.d=example.com header.s=s1 header.b=Qk3vR2mA;
        dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=example.com

Three checks sit in that line, and they answer three different questions. The fourth column is the one worth reading twice.

CheckWhat a pass meansWhat a fail usually meansWhat neither of them proves
spf=The machine that handed the message over is on the list that the envelope sender’s domain publishes in DNS.Either an impostor, or an ordinary forward: SPF breaks whenever a third party relays a message, which is not a fault and happens constantly.Anything about the address in From:. SPF never looks at it.
dkim=A cryptographic signature over the message matches a public key published by the signing domain.Either the message was altered in transit — a mailing list appending a footer is enough — or it was not signed by the domain it claims.That the signing domain is the domain in From:. Anyone can sign their own mail perfectly.
dmarc=SPF or DKIM passed and the domain it passed for lines up with the domain in From:.The domain in From: did not authorise this message — the closest a header ever comes to saying the sender is not who they say.That the message is safe, honest or wanted. A domain registered this morning can publish flawless DMARC by lunchtime.

That last column is the whole point, and it is where most reading of headers goes wrong. dmarc=pass proves that the domain printed in From: authorised the message. It says nothing whatsoever about whether that domain deserves anything from you — and publishing the records yourself takes about ten minutes, for anybody.

Five things a header cannot tell you

  • Where the sender is. The address of the machine somebody composed on is usually not in the block at all. The large webmail providers stopped publishing it years ago, and what is left is their own outbound server, which lives in a data centre and tells you the name of a hosting company.
  • Who the sender is. A domain is not a person. dmarc=pass for a domain bought forty minutes ago is a completely genuine pass.
  • Whether any of it is true. Authentication is about the envelope and never about the claim inside it. An invoice can be correctly signed, correctly aligned and entirely invented.
  • Whether anybody read it. Nothing in a header records that. What tries to is a tracking pixel in the body, which is a different mechanism with different defences.
  • When it was written. Date: comes from the sender’s own machine and the sender’s own clock. A message stamped three hours in the future is a misconfigured computer far more often than it is anything interesting; the timestamps you can rely on are in the Received lines your provider wrote.

And one entire family of fields proves nothing by construction. Anything beginning X- is a private extension invented by whoever wrote it, and a sender may write whatever they please: X-Spam-Status: No in a message means the message says it is not spam.

When the message landed at a disposable address

Be clear about what this service does and does not hand back. A message read here arrives parsed rather than raw: the sender, the subject, the date, the text, the HTML and the attachment list — the fields a script came for — and not the header block. The reading above is something you do in the mailbox that keeps your real mail.

GET /api/v1/message/{id} — what actually comes back
{
  "id":           "m_7Kq2fV3xTn",
  "from":         "support@example.com",
  "from_name":    "Example Support",
  "to":           "signup-2026@grabmail.io",
  "subject":      "Confirm your email address",
  "date":         "2026-09-05T09:14:22+00:00",
  "expires_at":   "2026-09-10T09:14:22+00:00",
  "text":         "Confirm your address: <https://example.com/confirm/9a31b0>",
  "text_derived": true,
  "html":         "<!doctype html><html>…",
  "attachments":  []
}

What a disposable address gives you instead is a signal no header field can match, and it works before you read a single line: you gave that address to exactly one party. A message arriving at it and claiming to come from somebody else was either forwarded by the party you gave it to, or sent by whoever they passed it on to. There is no third explanation and there is nothing to verify.

That is the same argument a plus tag makes, minus the part where the tag can be cut off in one line of code — here the address itself is different, so there is nothing to strip. It is also why a catch-all on a domain you own is the strongest version of it: one address per sign-up, kept for as long as you keep the domain, and Delivered-To: in your own mailbox names the one that got out.

The check you can still make

Look at where the link goes, not at what it says. The text field is either the sender’s own plain-text part or a rendering of the HTML — text_derived says which — and the rendering keeps every link target in angle brackets. So the address a button would have taken you to is sitting in the response, in plain sight, without loading the page, the images or the pixel.

the text field of a message that arrived as HTML only
Confirm your address: <https://example.com/confirm/9a31b0>

Not you? Ignore this message. <https://example.com/help>

The sixty-second version

  1. Open the source. Show original, View message source or Raw source, depending on which client you are in.
  2. Find Authentication-Results first. One line, written by your own provider, and the only one nobody else could have forged. dmarc=pass and the domain in From: really did authorise the message; dmarc=fail and it did not.
  3. Compare From: with Return-Path: and Reply-To:. They disagree for dull reasons all day long. They also disagree for the interesting one.
  4. Read the Received lines upwards from the bottom, and stop believing them at the first machine that is not your provider’s.
  5. Then look at where the links point, which is the part that decides what actually happens to you.

Everything above answers one narrow question: did the domain in From: authorise this message? That is a smaller question than “is this safe”, and it is worth answering anyway — it is the only one a header can answer at all.

Questions

How do I see the full header in Gmail?

Open the message, then the three-dot menu at its top right, then Show original. Gmail opens a new tab with the raw message and, above it, a small panel that has already graded SPF, DKIM and DMARC — which is the fastest verdict available anywhere, and it is free.

Can an email header be faked?

Most of it, yes. From:, Reply-To:, Date:, the subject and any number of invented Received: lines are all typed by the sender. What cannot be faked is what your own provider wrote when the message arrived: the top Received line and Authentication-Results. Read those two and treat the rest as testimony.

Can I find the sender’s IP address in a header?

Usually not the one you mean. Mail sent through any large webmail service carries the address of that service’s outbound server, not of the machine it was composed on; the providers stopped publishing the latter years ago, for the obvious reason. Mail sent by an application or a small server often does still show it, in brackets, in the bottom Received line — and that line is also the easiest one in the block to forge.

What is the difference between <code>From:</code> and <code>Return-Path:</code>?

From: is what the sender wants displayed and nothing checks it. Return-Path: is the envelope sender that the servers actually used, written into the header by the machine that accepted the message, and it is where bounces go. SPF is checked against Return-Path:, never against From:, which is why spf=pass on its own says less than it appears to. Lining the two up is DMARC’s entire job.

The header says <code>dmarc=fail</code>. Is the message a fake?

Not necessarily. Forwarding breaks SPF by design, and a mailing list that appends a footer breaks DKIM as well, so mail relayed through a list, a university or a “send it all to my other address” rule fails routinely while being entirely genuine. What the fail does mean is that nothing in the message proves the domain in From: stands behind it, so anything the message asks you to do deserves a second route to check it.

What is <code>Message-ID</code> for?

It is the message’s name — unique, assigned by the sending server, and the string that In-Reply-To and References point at to build a thread. Its practical use is that support desks and postmasters can look one up in their logs: quoting the Message-ID is the difference between “an email did not arrive yesterday” and a question somebody can actually answer.

Can I read the headers of mail sent to a temporary address here?

No — a message read here comes back parsed, as the sender, subject, date, text, HTML and attachment list, and the raw header block is not among the fields. The compensation is that a single-use address is its own evidence: it went to one party, so mail arriving from anybody else names the party that let it out, with no signature to verify. Messages are deleted after 5 days either way.

Try it while it is fresh

An address takes one click, no account and no card. Everything in this guide works on it straight away.

Welcome back

Your inboxes and your domains, in one place.