---
title: Late or in spam? How to read email headers with the Email Header Analyzer
description: A customer says your password-reset email arrived 45 minutes late, in the spam folder. To read email headers, copy the raw headers of that message, follow the Received lines from the bottom up to find where time was lost, and read Authentication-Results for the SPF, DKIM and DMARC verdict. Here's how with the free Email Header Analyzer, which runs in your browser, on a test message we built.
canonical: https://watchfor.io/blog/how-to-read-email-headers
---

# Late or in spam? How to read email headers with the Email Header Analyzer

2026-10-04 · WatchFor Team · Guides · guides, email, deliverability

A customer says your password-reset email arrived 45 minutes late, in the spam folder. To read email headers, copy the raw headers of that message, follow the Received lines from the bottom up to find where time was lost, and read Authentication-Results for the SPF, DKIM and DMARC verdict. Here's how with the free Email Header Analyzer, which runs in your browser, on a test message we built.

To read email headers, start from the raw headers of the message (Gmail:
**⋮ → Show original**). The `Received` lines are stamped by each server the
message passed through, newest on top, so read them **bottom up** and
compare timestamps to find where it waited. Then read the topmost
`Authentication-Results` header, written by the receiving server: it says
whether SPF, DKIM and DMARC passed, and for which domains.

The free [Email Header Analyzer](/email-header-analyzer) does both at once:
paste the headers and it lays out the delivery path with per-hop delays and
the authentication verdict. It runs entirely in your browser; nothing is
uploaded.

## What this tool answers

*Where did this message spend its time, and why did the receiver trust it
or not?* Reach for it when an email arrives late, lands in spam, or fails
DMARC, when a customer forwards a suspicious message that claims to be from
you, or to find the real sending IP for a blocklist check.

## A real example

### Reading email headers of a late message that landed in spam

The report: "your password-reset email took 45 minutes and went to spam".
Headers carry real addresses and IPs, so for this guide we built a test
message modelled on this common case, using reserved example domains and
documentation IPs: a shop at `example.com` sends through an email provider
at `example.net` to a customer at `example.org`.

We pasted its headers into the [Email Header Analyzer](/email-header-analyzer).
The first panels show the verdict:

![The Email Header Analyzer for the test message "Reset your password" from Example Shop no-reply@example.com to jordan@example.org: total delivery time 46m 40s, chips SPF pass, DKIM pass, DMARC fail, ARC not found, From and Return-Path on different domains, DKIM key d=example.net s=s1; the delivery compliance panel shows Authentication problems: DMARC Compliant failed (receiver reported dmarc=fail), SPF Authenticated passed for example.net, SPF Alignment failed example.net vs From example.com, DKIM Authenticated passed, DKIM Alignment failed d=example.net vs From example.com](/blog/how-to-read-email-headers-verdict.webp)

**Why spam.** SPF passed and DKIM passed, yet DMARC failed. The compliance
panel shows why: SPF passed for `example.net` (the provider's bounce
address in `Return-Path`), and DKIM passed with `d=example.net` (the
provider's signature). Neither is the domain in the From header,
`example.com`. DMARC requires at least one of them to pass *for the From
domain*. With no aligned pass, the receiver applied the shop's policy,
`p=QUARANTINE`, and filed the message as spam.

Further down, the delivery path shows where the time went:

![The delivery path of the test message, 4 hops origin first: app-01 to smtp-relay.example.net at 14:02:03 UTC, smtp-relay.example.net to out-7.example.net at 14:02:05 (+2s), out-7.example.net to mx1.example.org at 14:48:41 (+46m 36s, 100% of the delay, red bar), mx1.example.org to mailstore.example.org at 14:48:43 (+2s)](/blog/how-to-read-email-headers-path.webp)

**Why late.** Hops 1 and 2 (inside the provider) took 2 seconds each. Hop 3,
from the provider's outbound server to the customer's mail server, took
**46 minutes 36 seconds**, all of the delay. An `X-Greylist: delayed 2796
seconds` header added by the receiving server explains it: the customer's
server temporarily refused the first attempt (greylisting), and the provider
retried 46 minutes later.

**The answer:** two separate causes. The delay sits with the receiver's
greylisting and the provider's retry schedule, not with your app. The spam
placement is your configuration: turn on the provider's custom domain
authentication, so it signs with `d=example.com` and uses a bounce
subdomain such as `bounces.example.com`. Both then align, and DMARC passes.

## How to read the results

| What you see | What it means |
| --- | --- |
| **Subject, From → To, Date** | Taken from the headers as written by the sender. The From address is what DMARC protects. |
| **Total delivery time** | From the first `Received` timestamp to the last. |
| **SPF / DKIM / DMARC / ARC chips** | The receiver's verdicts from the topmost `Authentication-Results` header. Green pass; red fail, permerror or policy; amber softfail, neutral or temperror; grey "not found". |
| **From ↔ Return-Path** | Whether the visible sender and the envelope (bounce) address share a domain. Different domains is common with providers, and it means SPF can't align. |
| **DKIM key: d= s=** | The domain and selector from the `DKIM-Signature`. Check the selector with the [DKIM Checker](/dkim-record-checker). |
| **Delivery compliance** | The DMARC logic as a checklist: each mechanism must be authenticated *and* aligned with the From domain; one aligned pass is enough. The receiver's own `dmarc=` verdict wins when present. |
| **Published DNS records** | Optional: fetches the live SPF, DMARC and DKIM records for the From domain via Cloudflare's public DNS. Only those domain names leave your browser. They show today's records, which may differ from what the receiver saw at delivery. |
| **Delivery path** | One row per `Received` header, origin first, with the protocol and the time added at each hop. Green under 30 seconds, amber from 30, red from 2 minutes; the bar shows each hop's share of the total. |
| **Key headers / All headers** | Every header, grouped and filterable, with copy buttons. |

## More ways to use it

- **Checking a phishing email that uses your name.** Look at
  `Authentication-Results`: a forged message shows `dmarc=fail` with
  your domain in `header.from`, and the first `Received` line names the real
  origin.
- **Finding the sending IP for a blocklist check.** The IP in brackets on
  the lowest `Received` line written by a server you trust is the sender
  your recipient saw. Then run the
  [blacklist guide's](/blog/how-to-check-if-your-ip-is-blacklisted) check.
- **Proving a delay wasn't yours.** If the gap sits on the hop into the
  recipient's server, show them the timestamps.
- **Verifying a new provider setup.** Send yourself a test message and
  confirm `dkim=pass header.d=` shows your own domain.

## Tips and gotchas

- **Get the full raw headers.** Gmail: **⋮ → Show original**. Outlook on the
  web: **… → View → View message details**. Outlook for Windows:
  **File → Properties**, "Internet headers". Apple Mail:
  **View → Message → Raw Source**. Forwarding a message replaces most of
  its headers, so ask for the original's headers, not a forward.
- **Only trust the top of the chain.** Each server adds its lines above the
  existing ones, so the lower lines come from the sender and can be faked.
  The analyzer keeps the first `Authentication-Results` it finds, which is
  the receiving server's.
- **Clocks drift.** Small negative or odd gaps come from servers with
  skewed clocks; the analyzer clamps negatives to zero. Look for the big
  gap, not single seconds.
- **Fixing alignment.** In SendGrid, Mailgun, Postmark, Amazon SES and
  similar services the setting is called domain authentication or a custom
  sending domain. It gives you DNS records (DKIM CNAMEs, a bounce
  subdomain) to publish. The [DMARC guide](/blog/how-to-check-a-dmarc-record)
  covers policies, and
  [SPF, DKIM and DMARC explained](/blog/spf-dkim-dmarc-explained) the
  background.

## Check it from the command line too

Saved as an `.eml` file, a message's headers are plain text. Pull the
`Received` lines and their timestamps (the date is on the continuation
line):

```bash
grep -E '^Received:|^\s+[A-Z][a-z]{2}, [0-9]' message.eml
```

```text
Received: from mx1.example.org (mx1.example.org [203.0.113.5])
	Tue, 29 Sep 2026 14:48:43 +0000
Received: from out-7.example.net (out-7.example.net [198.51.100.37])
	Tue, 29 Sep 2026 14:48:41 +0000
Received: from smtp-relay.example.net (smtp-relay.example.net [198.51.100.25])
	Tue, 29 Sep 2026 14:02:05 +0000
Received: from app-01 (app-01 [10.0.4.12])
	Tue, 29 Sep 2026 14:02:03 +0000
```

The gap of the slow hop, in seconds:

```bash
echo $(( $(date -d "Tue, 29 Sep 2026 14:48:41 +0000" +%s) - $(date -d "Tue, 29 Sep 2026 14:02:05 +0000" +%s) ))
```

```text
2796
```

That matches the `X-Greylist: delayed 2796 seconds` header. And the
receiver's verdict:

```bash
grep -i -A3 '^Authentication-Results:' message.eml
```

```text
Authentication-Results: mx1.example.org;
 spf=pass (mx1.example.org: domain of bounces+18273@example.net designates 198.51.100.37 as permitted sender) smtp.mailfrom=bounces+18273@example.net;
 dkim=pass header.d=example.net header.s=s1;
 dmarc=fail (p=QUARANTINE) header.from=example.com
```

## FAQ

**How do I read email headers?**
Open the raw headers, read the `Received` lines from the bottom (the
sender) to the top (your mailbox) and compare timestamps for delays. Then
check `Authentication-Results` for `spf=`, `dkim=` and `dmarc=` and the
domains they name.

**How can I tell where an email was delayed?**
Find the two consecutive `Received` timestamps with the biggest gap. The
server in the later line held or retried the message. Gaps on the hop into
the recipient's server often come from greylisting or spam scanning.

**Why does DMARC fail when SPF and DKIM pass?**
Because they passed for a different domain than the one in your From
header, typically your email provider's. DMARC needs at least one of them
aligned with the From domain. Set up the provider's custom domain
authentication so it signs and bounces as your domain.

---

Canonical page: https://watchfor.io/blog/how-to-read-email-headers · All articles: https://watchfor.io/blog/all.md · Site guide: https://watchfor.io/llms.txt
