Skip to main content
by Meysam Azad
21 min read

How to Read Email Headers: One Real Dump, Line by Line

An email header is the message’s delivery record — every server that touched it, every authentication check that ran, and every verdict those checks produced. Learning how to read email headers is how you decide whether a specific message is what it claims to be. The trick is knowing which lines your own infrastructure wrote and which came from whoever sent it.

You are probably here with one message open: a user forwarded something that looks like it came from the CFO, or legitimate forwarded mail keeps landing in Junk. Note that this is not an aggregate report — reading a DMARC aggregate report is a different job, summarizing millions of messages in XML, where a header is the record of exactly one. Below: a complete redacted dump read top to bottom, the trust boundary drawn explicitly, the ARC set dissected tag by tag, and the vendor X-* headers decoded against their publishers’ own keys.

Key finding

40.8% of scanned domains publish no email authentication at all (2,243,877 of 5,499,028)

Source: DMARCguard, State of Email Authentication 2026 (scan 2026-02-27)

For senders like those, your Authentication-Results line reads spf=none dkim=none dmarc=none. A none is not a fail — it means nothing was published to check, so the header tells you only that the domain never made a claim you could test.

Where to find the raw headers

Every mail client exposes the raw source somewhere: Gmail’s “Show original”, Outlook on the web’s “View message source”, Apple Mail’s View → Message → All Headers, Thunderbird’s Ctrl+U. Export the whole thing, not a screenshot of the summary pane — the fields you need are the ones the client hides.

Our retrieval reference already has the per-client steps in a table, so use it to pull the raw source from your mail client, then come back with the text. A raw header decoder only works on what you paste into it, and clients reorder and pretty-print for display — the physical order of the lines is itself evidence, as the ARC section shows.

Every sample here is redacted the same way, so you can line yours up against ours: mailboxes become [email protected], hostnames mail.example.net, subjects are replaced, addresses move into the documentation ranges 192.0.2.0/24, 198.51.100.0/24 and 2001:db8::/32, and base64 is truncated with an ellipsis, never invented. Field order of every retained line is exactly as captured; about ten diagnostic X-* fields were dropped for length, none from between the lines shown. Both captures are public list archives — MIT, 2025-12-09, Microsoft 365 at the edge, and the IEEE 802.3 EFMP list, 2026-03-26, a Google two-hop chain. We link the archive index, not the messages, because a direct link would undo the redaction.

How to read email headers: the trust order, not the time order

Read an email header outward from your own infrastructure. The newest Received: line sits at the top and the oldest at the bottom, so the physical top of the file is the last hop before delivery — but the useful reading order is not “bottom to top”, it is “from the first line your own servers wrote, upward”. Everything below that line is the sender’s account of events.

Here is a real message, captured from the MIT mailing-list archive on 2025-12-09 and redacted as above. The recipient organization runs Microsoft 365 at its edge and an internal list host behind it, so two infrastructures appear.

One complete message header, redacted — read the trust boundary marker email
# Real message, captured 2025-12-09 from a public mailing-list archive.
# Redacted: mailboxes -> user@example.com, sender domains -> example.net /
# example.org / esp.example, subject line replaced, tenant names and GUIDs
# replaced, country code masked, base64 truncated with "..." and never invented.
# Addresses moved into the documentation ranges 192.0.2.0/24, 198.51.100.0/24
# and 2001:db8::/32; the one RFC 1918 address (10.167.242.41, inside the
# receiving infrastructure) is left as captured because it identifies nothing.
# Microsoft's own shared infrastructure hostnames are left intact -- recognizing
# them is the point. Field order of every RETAINED line and every tag are
# exactly as captured; roughly ten diagnostic X-* fields were dropped for
# length, and none of the dropped lines sat between the ones shown.

Return-Path: <bounces+SRS=aBcDe=1X@exampleorg.onmicrosoft.com>
Delivered-To: user@example.com
Received: (qmail 10692 invoked from network); 9 Dec 2025 05:35:35 -0000
Received: from mail-westcentralusazon11013009.outbound.protection.outlook.com
 (HELO CY3PR05CU001.outbound.protection.outlook.com) (198.51.100.24)
  by list.example.com with SMTP; 9 Dec 2025 05:35:35 -0000
ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none;
 b=pjhLg4r9H8RErNashOh7G5pdILkjxL0jg1+7Jl7pOwsEsIEzKisa9Car0GXVBk2AglXGxh5...
ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com;
 s=arcselector10001;
 h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1;
 bh=2ufHaa+foYewDMWg/jX9v2weX+vKIraWnocUpjD6VxU=;
 b=nf1GP9LHHPsrsZajp85YjFrNOus1iUO7MCjlMbo5AZEfVCnYM7pVRPEk7cbonQOqh3s...
ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass (sender ip is
 192.0.2.10) smtp.rcpttodomain=example.com
 smtp.mailfrom=example.net; dmarc=bestguesspass action=none
 header.from=example.net; dkim=pass (signature was verified)
 header.d=example.net; dkim=pass (signature was verified)
 header.d=fbl.esp.example; arc=none (0)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=exampleorg.onmicrosoft.com; s=selector2-exampleorg-onmicrosoft-com;
 h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck;
 bh=2ufHaa+foYewDMWg/jX9v2weX+vKIraWnocUpjD6VxU=;
 b=lT9mrgkoW8MI9YtgzntSgZyWaO5nb6nVg1kZZh93/TWwn9powZVsBFbzGmLxo2VgU/o...
Received: from DM6PR06CA0043.namprd06.prod.outlook.com (2001:db8:5:54::20) by
 DS4PR01MB9275.prod.exchangelabs.com (2001:db8:8:281::14) with Microsoft SMTP
 Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id
 15.20.9388.14; Tue, 9 Dec 2025 05:35:32 +0000
Received: from CY4PEPF0000EE35.namprd05.prod.outlook.com
 (2001:db8:5:54:cafe::d0) by DM6PR06CA0043.outlook.office365.com
 (2001:db8:5:54::20) with Microsoft SMTP Server (version=TLS1_3,
 cipher=TLS_AES_256_GCM_SHA384) id 15.20.9388.14 via Frontend Transport; Tue,
 9 Dec 2025 05:35:31 +0000
Authentication-Results: spf=pass (sender IP is 192.0.2.10)
 smtp.mailfrom=example.net; dkim=pass (signature was verified)
 header.d=example.net;dkim=pass (signature was verified)
 header.d=fbl.esp.example;dmarc=bestguesspass action=none
 header.from=example.net;
Received-SPF: Pass (protection.outlook.com: domain of example.net
 designates 192.0.2.10 as permitted sender)
 receiver=protection.outlook.com; client-ip=192.0.2.10;
 helo=out21-142.mail.example.net; pr=C

# ---------------- TRUST BOUNDARY ----------------
# The line below is the lowest Received: this organization's own
# infrastructure wrote. Everything ABOVE it is attested. Everything
# BELOW it is the sender's account of events.

Received: from out21-142.mail.example.net (192.0.2.10) by
 CY4PEPF0000EE35.mail.protection.outlook.com (10.167.242.41) with Microsoft
 SMTP Server (version=TLS1_3, cipher=TLS_AES_256_GCM_SHA384) id 15.20.9412.4
 via Frontend Transport; Tue, 9 Dec 2025 05:35:30 +0000
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
	d=example.net; s=esp-relay-01;
	t=1765258528; h=Date:From:To:Message-ID:Subject:MIME-Version:Content-Type;
	bh=2ufHaa+foYewDMWg/jX9v2weX+vKIraWnocUpjD6VxU=;
	b=Rrj9vDUobOOv+0QH063neCNcWOsFqFEZeU7P4kFkbuOcxydJslIcvwDXmZIkYWOUwm9...
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
	d=fbl.esp.example; s=feedback;
	t=1765258528; h=Date:From:To:Message-ID:Subject:MIME-Version:Content-Type;
	bh=2ufHaa+foYewDMWg/jX9v2weX+vKIraWnocUpjD6VxU=;
	b=2ObMkjNAdlD6AzgRWJdJ+VXACDac45u0NN15UcszG0pl87pFIiWAzCkKodQtN2c6sQO...
Received: from mta-07(mailfrom:sales@example.net fp:ma_600000253313635856 cluster:AY35D)
          by smtp.mail.example.net(127.0.0.1);
          Tue, 09 Dec 2025 13:35:05 +0800
Date: Tue, 09 Dec 2025 13:35:05 +0800
From: "Sales" <sales@example.net>
Return-Path:
	random_NjAwMDAwMjUzMzEzNjM1ODYx@example.net
To: <user@example.com>
Reply-To: <replies@example.org>
Message-ID: <d64d0d50-3da6-4820-974a-19dc2bbd7093@mail.esp.example>
Subject: Product enquiry - replacement parts
MIME-Version: 1.0
Content-Type: text/html; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable
X-Microsoft-Antispam:
	BCL:0;ARA:13230040|61400799027|48200799018|9140799003|...;
X-Forefront-Antispam-Report:
	CIP:192.0.2.10;CTRY:XX;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:out21-142.mail.example.net;PTR:out21-142.mail.example.net;CAT:NONE;SFS:(13230040)(61400799027)(48200799018)(9140799003)(376014);DIR:OUT;SFP:1101;
X-OriginatorOrg: exampleorg.onmicrosoft.com
X-MS-Exchange-CrossTenant-OriginalArrivalTime: 09 Dec 2025 05:35:30.7365 (UTC)
X-MS-Exchange-CrossTenant-AuthAs: Anonymous
X-MS-Exchange-CrossTenant-FromEntityHeader: Internet

Walk it in five passes. The Received: stack (RFC 5321) is the delivery path: each hop prepends a line naming who it received from, which host took it by, and when. Authentication-Results (RFC 8601) carries the SPF, DKIM and DMARC verdicts the receiving side computed — here it sits above the boundary line, exactly where the spec expects a trustworthy one to be. The ARC block records what an intermediary saw. From, Reply-To and Return-Path are the identity claims, and here they disagree three ways: the Reply-To domain is not the From domain, the Message-ID domain is neither, and two Return-Path values appear because a relay rewrote the envelope sender. The vendor X-* block at the bottom carries the filtering verdict.

Now the correction that matters most. “Read bottom to top” is the right chronological order and the wrong trust order. The bottom Received: line is where the sender claims the message originated, which is exactly where someone fabricating one puts it. A 2007 AnandTech thread makes the mistake and corrects it in-thread — “you can only ‘trust’ the last header you can verify” — and that correction has held for nearly two decades. That thread is old; the behavior is not. It is cited for durable behavior, never for a number.

One arithmetic trap: each Received: line carries the recording server’s own local offset. Above, the sender’s hop stamps 13:35:05 +0800 and the receiving hop 05:35:30 +0000 — the same wall-clock instant, thirty seconds apart. Subtract the raw clock faces and the message appears to arrive eight hours before it was sent. Normalize every hop to UTC before you do any arithmetic at all.

Two side-by-side panels over the same four-hop Received stack. The left panel, labeled Chronological order, numbers the hops from the bottom line upward. The right panel, labeled Trust order, starts at the first Received line the recipient's own infrastructure wrote and reads outward, marking every line below it as the sender's account.
The same stack, two reading orders. Chronology starts at the bottom; trust starts at your own first line and never goes below it.

What is the trust boundary in an email header?

The trust boundary is the first Received: line your own mail infrastructure added — the lowest line in the file whose by clause names a host you operate. Lines above it were recorded by hosts you run and can be trusted; lines below it were supplied by the sender and can be fabricated. The same rule governs Authentication-Results: only a line carrying your own authserv-id is evidence, because a message can arrive with a forged one already in it.

That is not a heuristic we invented. RFC 8601 §5 requires a conforming MTA to delete any Authentication-Results header claiming, by its authserv-id, to have been added inside its own trust boundary but which did not come from another trusted MTA. §7.1 spells out why: an attacker can forge the header using the receiving domain’s own name, and a non-conformant MTA will pass it straight through to the mail client.

Forgeable or attested
Every field in a header dump, sorted by whether it is evidence.
Field Who writes it Trustworthy? What it actually proves
From / display name Sender No Nothing on its own — DMARC alignment is the test
Reply-To Sender No Where replies go, chosen by the sender
Message-ID / Date Originating client No Nothing; a mismatched Message-ID domain is a tell, not proof
X-Originating-IP / X-Sender-IP Outside your boundary No Non-authoritative; the same class of header as X-Forwarded-For
Received: below your boundary Sender or upstream No The sender's account of the path
Received: at or above your boundary Your infrastructure Yes The connecting IP your server actually saw
Authentication-Results under your authserv-id Your infrastructure Yes The verdicts your server computed
Authentication-Results under any other authserv-id Unknown No Nothing — it may have been planted by the sender
Verdict Trust nothing below the first Received: line your own servers wrote — and no Authentication-Results line that is not stamped with your own authserv-id.

SpamAssassin operationalizes the same split: internal_networks (your MX and relays) should be a subset of trusted_networks (those plus external relays you trust not to forge headers), and each defaults to the other when only one is set. Both are exposed as the X-Spam-Relays-Trusted and -Untrusted pseudo-headers, with authres_trusted_authserv gating the same decision for Authentication-Results. Draw the boundary one hop too wide and it shows: behind NAT, SpamAssassin cannot tell from Received: lines whether your MX has a public address, assumes it does, and fires ALL_TRUSTED on inbound spam.

One more distinction if you want to decode email header fields correctly: SPF (RFC 7208) evaluates the envelope sender, the MailFrom of RFC 5321, which is what Return-Path records. DMARC (RFC 9989) then tests whether the visible RFC 5322 From aligns with whatever passed. An spf=pass alone vouches for the envelope, not for the name your user sees — Microsoft documents the same 5321-versus-5322 split.

A single header stack rendered as abbreviated lines with a bold horizontal rule drawn through it. Lines above the rule are teal and labeled attested — written by hosts you operate. Lines below are red and labeled testimony — supplied by the sender. The rule itself is labeled first Received your infrastructure wrote.
The boundary is a position in the file, not a field name. Find it once and every other question answers itself.

How to read an ARC set: ARC-Seal, ARC-Message-Signature, ARC-Authentication-Results

An ARC set is three headers written together at one hop, defined in RFC 8617. ARC-Authentication-Results (AAR) records the verdicts that hop computed, ARC-Message-Signature (AMS) signs the message as that hop saw it, and ARC-Seal (AS) signs the chain itself and carries cv=, the chain-validation status. All three share an instance number i=, and a forwarded message carries one complete set per hop.

A two-hop Google chain (i=1, i=2) plus a Microsoft set for contrast email
# A two-hop Google chain, captured 2026-03-26 from a public standards-list
# archive (Gmail sender -> LISTSERV mailing list -> Gmail recipient), plus a
# Microsoft set for contrast. Redacted per the convention in this post;
# base64 truncated with "..." and never invented.
#
# NOTE ON ORDER: the source archive renders header fields alphabetically, so
# the grouping below is by instance, not wire order. In a RAW dump the observed
# physical order within one set is ARC-Seal -> ARC-Message-Signature ->
# ARC-Authentication-Results, and the i=2 set sits ABOVE the i=1 set.

# ---- Google, instance 1: the origin hop ----

ARC-Seal: i=1; a=rsa-sha256; t=1774552407; cv=none; d=google.com;
 s=arc-20240605;
 b=FYoRL24mDIPQ3QGYABAxNwRrjObaCFAcKRMV16hkh2CxurYhuCIdX608UBD0rbh/u6...
ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com;
 s=arc-20240605;
 h=to:subject:message-id:date:from:mime-version:dkim-signature;
 bh=EPXSm8djzZDDzCjsfhHsyKYYHxgNzzDg4bqqd3Tvc7k=;
 fh=eNOXSQWankAFvswhJk6Gounc4+lPTV6KRKuORC+YdQI=;
 b=Dx5a0BugxavAvo83XQjIwufw5O5eGW56up8zvw6uRl9wVhtLqF480Jyywomb/dF0+P...;
 darn=listserv.example.net
ARC-Authentication-Results: i=1; mx.google.com; arc=none
#   ^ cv=none at i=1 is mandatory (RFC 8617). arc=none in the AAR says this
#     sealer found no chain to inherit -- it is the first link.

# ---- Google, instance 2: the hop after the mailing list ----

ARC-Seal: i=2; a=rsa-sha256; t=1774552409; cv=pass; d=google.com;
 s=arc-20240605;
 b=CH02yJcjGJ5KTPBas4hn0TnAM7+BW+c4rEkPOSl0Tu5nStJv46LBWvZFw4V+SyeTbx...
ARC-Message-Signature: i=2; a=rsa-sha256; c=relaxed/relaxed; d=google.com;
 s=arc-20240605;
 h=to:subject:message-id:date:from:mime-version:dkim-signature;
 bh=EPXSm8djzZDDzCjsfhHsyKYYHxgNzzDg4bqqd3Tvc7k=;
 fh=eNOXSQWankAFvswhJk6Gounc4+lPTV6KRKuORC+YdQI=;
 b=EtGVhU/pwQW77P1PvGzUlRXmkdM6WPca7EWY00+bblkq6CxMjhClgGxycB/bUv8RNk...;
 dara=google.com
ARC-Authentication-Results: i=2; mx.google.com;
 dkim=pass header.i=@example.com header.s=google header.b="IU/01rcm";
 arc=pass (i=1);
 spf=pass (google.com: domain of user@example.com designates 192.0.2.1 as
 permitted sender) smtp.mailfrom=user@example.com;
 dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=example.com;
 dara=neutral header.i=@listserv.example.net
#   ^ THIS is the mechanism. The i=2 AAR carries the SPF, DKIM and DMARC
#     verdicts Google computed at that hop, plus arc=pass (i=1) naming the
#     instance it validated. The bh= at i=1 and i=2 is byte-identical, so the
#     body never changed and the chain held.
#     Note the asymmetry: the i=1 AAR above carries ONLY arc=none. Gmail's
#     origin seal records no SPF/DKIM/DMARC verdicts at all, so in THIS chain
#     the earliest verdicts sit at i=2. RFC 8617's DMARC-override model reads
#     the i=1 AAR for the original results -- that is the rule; this capture
#     is Gmail behaviour, not a counter-example to it.
#
#     fh=, dara= and darn= are NOT in RFC 8617. All three are defined in
#     draft-chuang-replay-resistant-arc-11 (W. Chuang, Google; B. Gondwana,
#     Fastmail; 20 February 2024), an Internet-Draft that has since EXPIRED,
#     so treat the meanings as provisional:
#       fh=   base64 hash over the recipient-disclosure headers, so a verifier
#             can prove they were not altered
#       darn= the recipient domain name when that receiver is NAIVE of the
#             DARA protocol -- here darn=listserv.example.net at i=1, i.e.
#             "the next hop is a list that does not speak DARA"
#       dara= the receiver's ARC-Seal d= domain when the receiver IS DARA
#             capable -- here dara=google.com at i=2
#     The draft places these tags on the DKIM-Signature or ARC-Seal; Google
#     writes them on the ARC-Message-Signature. In the AAR, dara= is a result
#     (pass / fail / neutral) -- dara=neutral above.

# ---- Microsoft, instance 1, for contrast ----

ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none;
 b=pjhLg4r9H8RErNashOh7G5pdILkjxL0jg1+7Jl7pOwsEsIEzKisa9Car0GXVBk2AglXGxh5...
#   ^ no t= timestamp, and s= before d= -- Google writes t= and puts d= first.
ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com;
 s=arcselector10001;
 h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1;
 bh=2ufHaa+foYewDMWg/jX9v2weX+vKIraWnocUpjD6VxU=;
 b=nf1GP9LHHPsrsZajp85YjFrNOus1iUO7MCjlMbo5AZEfVCnYM7pVRPEk7cbonQOqh3s...
#   ^ no fh=, no dara= -- and h= is dominated by Microsoft's own X-MS-* names.
ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass (sender ip is
 192.0.2.10) smtp.rcpttodomain=example.com smtp.mailfrom=example.net;
 dmarc=bestguesspass action=none header.from=example.net;
 dkim=pass (signature was verified) header.d=example.net; arc=none (0)
#   ^ authserv-id "mx.microsoft.com 1", a smtp.rcpttodomain= property no other
#     sealer emits, dmarc=bestguesspass, and a numeric reason after arc=.

Read cv= first, because it has an invariant you can check at a glance. RFC 8617 requires cv=none on i=1 and cv=pass on every instance above it; a cv=fail anywhere means the chain is broken and cannot recover. Instance numbers run 1 to N with no gaps, capped at 50.

Then read down, not up. After cv=, the instance that matters is i=1: RFC 8617’s DMARC-override model has the receiver examine the i=1 AAR for the original authentication results, because that is the hop closest to origin. Higher instances record what each later intermediary saw. Our free ARC chain analyzer walks a set in that order for you, but the rule is worth holding in your head, because plenty of guides tell you to read the outermost AAR and stop.

Here is what the capture above actually shows. At i=2 the AAR carries the verdicts Google computed at that hop — spf=pass, dkim=pass, dmarc=pass (p=REJECT sp=REJECT dis=NONE) — plus arc=pass (i=1) naming the instance it validated. The bh= body hash at i=1 and i=2 is byte-identical, which is the proof the body was never altered, and is why the chain held. Note the asymmetry: the i=1 AAR in this chain carries only arc=none. Gmail’s origin seal records no SPF, DKIM or DMARC verdicts at all, so here the earliest verdicts available sit at i=2. That is Gmail behavior in this capture, not the general rule.

And a correction worth owning. The presence of an ARC set is not evidence that a message was forwarded through a third party. In the Microsoft set above, the AAR ends arc=none (0) — the sealer is telling you it found no prior chain to inherit. One set with cv=none and arc=none means “a sealer touched this and there was nothing before it,” which is a different statement entirely.

Sealers also differ inside the same RFC, and the differences are what let you recognize whose infrastructure you are looking at:

HeaderGoogle (arc-20240605)Microsoft (arcselector10001)
ASi= a= t= cv= d= s= b=i= a= s= d= cv= b= — no t=, and s= before d=
AMSi= a= c= d= s= h= bh= b= + fh=, dara=/darn=i= a= c= d= s= h= bh= b= — no fh=, no dara=
AARi= + mx.google.com + methodsi= + mx.microsoft.com 1 + smtp.rcpttodomain=
Tags observed in two dated captures (2025-12-09 Microsoft, 2026-03-26 Google) — a two-sealer sample, not a survey

Google’s AMS carries three tags RFC 8617 never mentions: fh=, plus darn= on the i=1 instance and dara= on i=2. They are not undocumented, only unstandardized. All three are defined in draft-chuang-replay-resistant-arc, an Internet-Draft by Wei Chuang (Google) and Bron Gondwana (Fastmail), version 11 dated 20 February 2024 — expired, not a standard, so treat the meanings as provisional. In that draft fh= is a hash over the recipient-disclosure headers so a verifier can prove they were not altered; darn= carries the recipient domain name when that receiver is naive of the draft’s DARA recipient-validation protocol; and dara= carries the receiver’s ARC-Seal d= domain when the receiver is DARA-capable. Read against the capture, the pair decodes cleanly: darn=listserv.example.net at i=1 says the next hop is a mailing list that does not speak DARA, and dara=google.com at i=2 names the DARA-capable receiver on the far side. The draft puts these tags on the DKIM-Signature or ARC-Seal; Google writes them on the AMS instead.

Microsoft’s AAR is the more distinctive of the two: the authserv-id mx.microsoft.com 1 carries a trailing numeric token, smtp.rcpttodomain= is a recipient-domain property that appears in neither of our other captures, dmarc=bestguesspass is Microsoft’s documented token for “no DMARC record exists, but the check would pass if one did”, and arc= closes with a numeric reason — arc=none (0) here, arc=fail (47) on a broken chain.

Physical order carries information too. In the raw MIT dump the order within one set is AS → AMS → AAR, sitting above the Received: lines that hop wrote. Sets are prepended like any other header, so i=2 necessarily sits above i=1 — that follows from the prepend rule, not from either capture: the MIT message has one set and the IEEE archive renders fields alphabetically. In practice i=2 is the ceiling you will meet.

One gap stated plainly, because that beats a reconstruction. Fastmail says it is “not only validating and adding ARC headers” but also using them between trusted systems (Fastmail, March 2023). That post does not name its signing domain or enumerate the three headers, and no dated public capture of Fastmail’s literal sealed triple could be found, so we print neither.

Three questions this section deliberately does not answer, each with a home: what ARC is, and how to configure trusted sealers in Microsoft 365 and Google Workspace; why a chain fails and what each reason string means; and which receivers seal ARC and which honor a seal.

Two stacked ARC sets in wire order. The i=2 set sits above the i=1 set, each showing ARC-Seal then ARC-Message-Signature then ARC-Authentication-Results. The i=1 seal is amber and marked cv=none, the i=2 seal is teal and marked cv=pass. A downward arrow labeled read down to i=1 for the original verdicts runs from the outermost seal to the i=1 ARC-Authentication-Results.
Sets are prepended, so the newest instance is physically highest. Read cv= at the top, then read down to i=1.

Why every provider writes Authentication-Results differently

RFC 8601 defines the syntax of Authentication-Results, but each provider populates it differently — different authserv-id, different method order, different optional properties. Knowing your provider’s dialect is what stops you reading a missing field as a failure.

Four Authentication-Results dialects email
# Four Authentication-Results dialects. Two are dated live captures, one is
# a dated capture over two years old, and one is a vendor-repo sample with no
# capture date -- each is labelled below, because "we could not date this" is
# part of the evidence. Redacted per the convention in this post. Line
# wrapping is ours; every token, its order and its spelling are as captured.

# ---- Microsoft 365 / Exchange Online (captured 2025-12-09) ----
# No leading authserv-id at all: the value starts straight at spf=.
# action=none is a bare token, not the ptype.property form RFC 8601 defines.
Authentication-Results: spf=pass (sender IP is 192.0.2.10)
 smtp.mailfrom=example.net; dkim=pass (signature was verified)
 header.d=example.net;dkim=pass (signature was verified)
 header.d=fbl.esp.example;dmarc=bestguesspass action=none
 header.from=example.net;

# ---- Google Workspace / Gmail (captured 2026-03-26, from the i=2 AAR) ----
# authserv-id is always mx.google.com. Richest DKIM property set of the four:
# header.i=, header.s= and a truncated header.b= fragment. The DMARC
# parenthetical carries all three of p=, sp= and dis=.
Authentication-Results: mx.google.com;
 dkim=pass header.i=@example.com header.s=google header.b="IU/01rcm";
 spf=pass (google.com: domain of user@example.com designates 192.0.2.1 as
 permitted sender) smtp.mailfrom=user@example.com;
 dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=example.com

# ---- Mimecast (VENDOR-REPO SAMPLE -- undated, provenance is a connector
#      repository, not a capture of live mail; shown for shape only) ----
# authserv-id relay.mimecast.com. Method order is dkim -> dmarc -> spf, with
# SPF last. Terse (policy=none) parenthetical. The spam verdict is NOT here --
# it lives in a separate integer X-Mimecast-Spam-Score: line.
Authentication-Results: relay.mimecast.com;
 dkim=pass header.d=example.com header.s=selector header.b=QuZ7ohIh;
 dmarc=pass (policy=none) header.from=example.com;
 spf=pass (relay.mimecast.com: domain of user@example.com designates
 192.0.2.1 as permitted sender) smtp.mailfrom=user@example.com

# ---- Zoho (captured 2021-09-15 -- over two years old; shown for shape) ----
# authserv-id mx.zohomail.com. Sparsest DKIM result of the four: a bare
# dkim=pass with no header.d, header.i or header.b. Reports arc= inline with
# a free-text reason in parentheses.
Authentication-Results: mx.zohomail.com;
 dkim=pass;
 spf=pass (zohomail.com: domain of mail.example.net designates 192.0.2.1 as
 permitted sender) smtp.mailfrom=user@example.com;
 arc=fail (Bad Signature)
Platformauthserv-idMethod orderDistinguishing token
Microsoft 365none emittedspf → dkim → dmarcbare action=none; compauth=pass reason=NNN (per docs)
Google Workspacemx.google.comdkim → spf → dmarcheader.b= fragment; (p= sp= dis=)
Mimecastrelay.mimecast.comdkim → dmarc → spfverdict lives in X-Mimecast-Spam-Score:
Zohomx.zohomail.comdkim → spf → arcbare dkim=pass, no header.d
Authentication-Results dialects. Rows are from the samples above except where a cell is attributed to vendor documentation.

Microsoft 365 is the outlier that trips parsers: its line starts straight at spf= with no leading authserv-id, which RFC 8601’s grammar requires. It also adds compauth, a composite-authentication result with a numeric reason that combines SPF, DKIM, DMARC and other signals against the From domain — that one comes from Microsoft’s documentation, not from our capture, which carries no compauth token. Google is the most verbose, populating header.i=, header.s= and a truncated header.b= on every DKIM result; its line above is lifted from the i=2 AAR, because the archive that preserved the message kept no top-level Authentication-Results. Mimecast reorders the methods and moves the spam verdict out of the header entirely — that sample comes from a vendor connector repository and carries no capture date, which is why the snippet labels it as such. Zoho emits the sparsest DKIM result of the four, a bare dkim=pass, from a 2021 capture shown only for shape.

Three dialects we could not verify, said out loud rather than reconstructed: Fastmail’s top-level line, a Proofpoint line with a *.ppops.net authserv-id, and a branded Barracuda authserv-id. Barracuda expresses its verdict through X-Barracuda-* and SpamAssassin-style headers instead, and hosted Proofpoint deployments typically show the downstream receiver as the authserv-id. If your dump has one of these, trust what you see over what any guide told you to expect.

The vendor X-headers worth decoding

X-* headers are vendor-specific and carry no cross-platform meaning, but a handful have published decoding keys that turn a bare number into a verdict. Microsoft’s Forefront report, Microsoft’s bulk complaint level, Mimecast’s spam score and SpamAssassin’s X-Spam-Status are the four you will hit most.

Vendor X-headers with their publishers' own decoding keys email
# Vendor X-* headers with published decoding keys, redacted per the convention
# in this post. Every key below was re-checked against the vendor's own
# documentation on 2026-08-19.

# ---- Microsoft: X-Forefront-Antispam-Report (captured 2025-12-09) ----
X-Forefront-Antispam-Report:
 CIP:192.0.2.10;CTRY:XX;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;
 H:out21-142.mail.example.net;PTR:out21-142.mail.example.net;CAT:NONE;
 SFS:(13230040)(61400799027)(48200799018);DIR:OUT;SFP:1101;
#
#   CIP:  the connecting IP address, as Microsoft's edge saw it
#   H:    the HELO/EHLO string the connecting server offered
#   PTR:  reverse DNS of the source IP
#   IPV:NLI  the IP was not found on any IP reputation list
#   SFV:NSPM spam filtering marked the message non-spam and delivered it
#   CAT:  which threat policy fired -- SPM spam, HSPM high-confidence spam,
#         PHSH phishing, SPOOF spoofing, BULK bulk, MALW malware. The CAT:NONE
#         in the capture above is observed-but-undocumented: NONE does not
#         appear in Microsoft's published CAT list.
#   DIR:  directionality -- INB inbound, OUT outbound, INT internal
#   SCL:  spam confidence level. Microsoft's current guidance: in cloud
#         organizations this value does NOT determine the verdict or the
#         action -- read CAT: and DIR: instead. SCL is primarily for
#         on-premises Exchange, where 5 or higher generally means "bad".

# ---- Microsoft: X-Microsoft-Antispam ----
X-Microsoft-Antispam: BCL:0;ARA:13230040|61400799027|...;
#
#   BCL:0        not from a bulk sender
#   BCL:1,2,3    bulk sender that generates few complaints
#   BCL:4,5,6,7  bulk sender that generates a mixed number of complaints
#   BCL:8,9      bulk sender that generates a high number of complaints
#   Threshold: 7 in the default anti-spam policy, 6 Standard preset,
#   5 Strict preset. At or above the threshold: Junk (default/Standard) or
#   Quarantine (Strict). PCL: on the same header is phish confidence.

# ---- Mimecast: X-Mimecast-Spam-Score ----
X-Mimecast-Spam-Score: 4
#
#   A single integer. 28 or higher is rejected in protocol regardless of any
#   policy. Below that, the hold band depends on the scan setting:
#   7-27 Relaxed, 5-27 Moderate, 3-27 Aggressive.

# ---- SpamAssassin: X-Spam-Status / X-Spam-Level ----
# (seen behind Barracuda, Zimbra, cPanel and amavisd deployments)
X-Spam-Status: No, score=-1.2 required=5.0
 tests=DKIM_SIGNED,DKIM_VALID,SPF_PASS,URIBL_BLOCKED
 autolearn=ham autolearn_force=no version=3.4.4
X-Spam-Level:
#
#   Yes/No is the verdict; score= is the total; required= is the configured
#   threshold; tests= names every rule that matched. X-Spam-Level renders the
#   rounded score as a run of asterisks -- empty here because the score is
#   negative.

X-Forefront-Antispam-Report is where a Microsoft 365 verdict actually lives. CIP: is the connecting IP, H: the HELO string, PTR: its reverse DNS, SFV: the filter verdict, CAT: the category that fired, and DIR: the directionality — the snippet above lists the values for each, and flags CAT:NONE as observed-but-undocumented, since NONE is not in Microsoft’s published category list.

SCL: is the trap, because most guides still print a key Microsoft has retired. Its current documentation says of the SCL value in cloud organizations: “The value doesn’t determine whether spam filtering identifies a message as Spam or High confidence spam, and it doesn’t determine the action taken on the message.” The same page notes that the same SCL value can therefore appear on messages with different verdicts, and tells you to read CAT: and DIR: instead. SCL keeps its meaning on-premises, where 5 or higher generally indicates a bad message.

Bulk complaint level on X-Microsoft-Antispam is one scale: BCL:0 is not bulk, 1–3 few complaints, 4–7 a mixed number, 8–9 a high number, with the default anti-spam policy acting at 7 (Standard preset 6, Strict 5). Mimecast’s spam score is one integer: 28 or higher is rejected in protocol regardless of policy, and below that the hold band follows the setting — 7–27 Relaxed, 5–27 Moderate, 3–27 Aggressive. SpamAssassin’s X-Spam-Status gives the verdict, score=, the required= threshold and every matched rule in tests=. For X-Gm-Message-State we found no published key — treat it as opaque until Google documents one.

Five things people get wrong when reading email headers

The five recurring misreads are: trusting the bottom Received: line as the origin, trusting an X-* IP header as authoritative, reading dkim=pass as proof the visible From is genuine, assuming your own DMARC record filters your inbox, and treating ARC headers as junk to be stripped.

The bottom line is not the origin. It is where the sender says the message started. Only the lowest line your own infrastructure wrote records an IP that someone other than the sender observed.

X-Originating-IP is not authoritative — and X-IronPort-RemoteIP fails for a different reason. X-Originating-IP is sender-supplied and sits below your boundary, which makes it the email equivalent of trusting a raw X-Forwarded-For. X-IronPort-RemoteIP is not that: your own Cisco appliance wrote it, so it is evidence — but only of the host that connected to the appliance, which is not where the message started if anything relayed it in between. A 2017 Cisco Community thread records the expectation that the field “should ideally contain the originating IP”. That thread is old; the behavior is not.

dkim=pass does not authenticate the From your user sees. A pass proves the domain in d= signed the message and it arrived intact (RFC 6376). Whether that says anything about the visible From is a question of alignment, which is DMARC’s job. A 2010 Cisco thread puts it exactly: “It came back as dkim=pass (1:0:0:good)… I’d imagine if someone tried to spoof, it would hardfail correct?” Old thread, durable behavior.

Your own DMARC record is not an inbound filter. It tells other receivers how to treat mail claiming to be from you; p=reject changes nothing about mail arriving from other domains. The one exception is what makes the thread below surprising: your own receiver does evaluate your DMARC record on inbound mail that claims to be from you. A 2022 Microsoft Q&A opens with exactly that situation — an admin whose organization “has already configured DKIM and DMARC” asking why Microsoft delivered a message spoofing their own domain that had already failed both checks. Old thread, durable behavior.

ARC headers are not junk. A 2020 Microsoft Q&A opens with an admin asking how to remove ARC-Authentication-Results from Exchange Online — that is a request to delete the only record of why forwarded mail was accepted. Strip the set and the next hop has nothing to validate.

FAQ

In what order should you read email headers?

Chronologically, the oldest Received: line sits at the bottom and the newest at the top. For trust, start at the first Received: line your own infrastructure wrote and read upward — everything below that line was supplied by the sender and can be fabricated.

What does arc=none (0) mean in an ARC-Authentication-Results header?

It means the sealer found no prior ARC chain to inherit, so this set is the first link. Microsoft appends a numeric reason code, which is why it writes arc=none (0). It is not a failure, and on its own it does not mean the message was forwarded.

Can email headers be faked?

Yes. From, Reply-To, Message-ID, Date, X-Originating-IP and every Received: line added before the message reached your servers can all be forged — including a planted Authentication-Results line. Only headers written by your own infrastructure are evidence, which is why RFC 8601 requires a conforming server to strip forged ones.

Which line in an email header shows the real sender IP?

The Received: line your own inbound server wrote. It records the IP that actually connected to you. Lines below it, and any X-Originating-IP or X-Sender-IP header, are the sender’s claims and are not authoritative — they were added outside your trust boundary, where anyone can write them.

Does dkim=pass mean the email is genuine?

No. A dkim=pass proves the domain in d= signed the message intact. It says nothing about the address the recipient sees. DMARC alignment is the test that connects the signing domain to the visible From, so compare header.d= against the From domain.

Conclusion

Knowing how to read email headers is not memorizing fields — it is knowing which lines are evidence and which are testimony. Four moves, every time:

  • Export the raw source, not a screenshot of the summary pane.
  • Find the first Received: line your own infrastructure wrote and treat it as the boundary.
  • Read only the Authentication-Results stamped with your own authserv-id.
  • Check the ARC set if the message was forwarded: cv= on the outermost seal first, then down to the i=1 AAR.