---
title: "How to Read Email Headers: Decode the ARC Set"
description: "How to read email headers line by line: trace the Received chain, find your trust boundary, and decode the ARC set. Walk one real dump, then analyze yours."
publishedAt: 2026-09-03
tags: ["email-headers", "arc", "authentication-results", "rfc-8601", "rfc-8617", "email-forensics", "email-authentication"]
faq:
  - question: "In what order should you read email headers?"
    answer: "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."
  - question: "What does arc=none (0) mean in an ARC-Authentication-Results header?"
    answer: "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."
  - question: "Can email headers be faked?"
    answer: "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."
  - question: "Which line in an email header shows the real sender IP?"
    answer: "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."
  - question: "Does dkim=pass mean the email is genuine?"
    answer: "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."
---
# How to Read Email Headers: One Real Dump, Line by Line

<TLDR>
  Read email headers from your own infrastructure outward, not from the bottom
  up. Find the first `Received:` line your own mail servers wrote — that is
  your trust boundary. Everything above it was recorded by hosts you control
  and can be trusted; everything below it, including `From`, `Reply-To`,
  `Message-ID` and every earlier `Received:` line, was written by the sender
  and can be forged. Only the `Authentication-Results` line stamped with your
  own authserv-id is authoritative. If the message was forwarded, the ARC set
  carries the verdicts each hop computed.
</TLDR>

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](/blog/how-to-read-dmarc-report/),
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.

<KeyStat
  stat="40.8%"
  label="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)"
  sourceHref="/research/email-authentication/"
/>

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](/tools/email-header-analyzer/),
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 `user@example.com`, 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](https://www.ieee802.org/3/EFMP/email/), 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.

<CodeBlock
  lang="email"
  filename="One complete message header, redacted — read the trust boundary marker"
  code={fullHeaderDump}
/>

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](https://forums.anandtech.com/threads/ip-address-identificactions.50301/post-23596232)
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.

<Figure
  src="/images/blog/read-email-headers/read-email-headers_read-order_flowchart.svg"
  alt="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."
  caption="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.

<VerdictTable
  title="Forgeable or attested"
  caption="Every field in a header dump, sorted by whether it is evidence."
  columns={["Field", "Who writes it", "Trustworthy?", "What it actually proves"]}
  rows={[
    [
      "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](https://cwiki.apache.org/confluence/display/SPAMASSASSIN/TrustedRelays),
with
[`authres_trusted_authserv`](https://spamassassin.apache.org/full/4.0.x/doc/Mail_SpamAssassin_Plugin_AuthRes.html)
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](https://cwiki.apache.org/confluence/display/spamassassin/TrustPath).

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](https://learn.microsoft.com/en-us/defender-office-365/anti-phishing-protection-spoofing-about).

<Figure
  src="/images/blog/read-email-headers/read-email-headers_trust-boundary_diagram.svg"
  alt="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."
  caption="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.

<CodeBlock
  lang="email"
  filename="A two-hop Google chain (i=1, i=2) plus a Microsoft set for contrast"
  code={arcSetTwoHop}
/>

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:

<DataTable caption="Tags observed in two dated captures (2025-12-09 Microsoft, 2026-03-26 Google) — a two-sealer sample, not a survey">

| Header | Google (`arc-20240605`)                             | Microsoft (`arcselector10001`)                       |
| ------ | --------------------------------------------------- | ---------------------------------------------------- |
| AS     | `i= a= t= cv= d= s= b=`                             | `i= a= s= d= cv= b=` — no `t=`, and `s=` before `d=` |
| AMS    | `i= a= c= d= s= h= bh= b=` + `fh=`, `dara=`/`darn=` | `i= a= c= d= s= h= bh= b=` — no `fh=`, no `dara=`    |
| AAR    | `i=` + `mx.google.com` + methods                    | `i=` + `mx.microsoft.com 1` + `smtp.rcpttodomain=`   |

</DataTable>

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](https://datatracker.ietf.org/doc/html/draft-chuang-replay-resistant-arc-11),
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](https://learn.microsoft.com/en-us/defender-office-365/message-headers-eop-mdo)
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"](https://www.fastmail.com/blog/what-is-arc/)
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](/learn/arc/) in Microsoft
365 and Google Workspace;
[why a chain fails and what each reason string means](/blog/arc-email-authentication/);
and
[which receivers seal ARC and which honor a seal](/blog/dmarc-forwarding-arc-fix/).

<Figure
  src="/images/blog/read-email-headers/read-email-headers_arc-set-anatomy_diagram.svg"
  alt="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."
  caption="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.

<CodeBlock
  lang="email"
  filename="Four Authentication-Results dialects"
  code={arDialects}
/>

<DataTable caption="Authentication-Results dialects. Rows are from the samples above except where a cell is attributed to vendor documentation.">

| Platform         | authserv-id          | Method order       | Distinguishing token                                    |
| ---------------- | -------------------- | ------------------ | ------------------------------------------------------- |
| Microsoft 365    | none emitted         | spf → dkim → dmarc | bare `action=none`; `compauth=pass reason=NNN` (per docs) |
| Google Workspace | `mx.google.com`      | dkim → spf → dmarc | `header.b=` fragment; `(p= sp= dis=)`                   |
| Mimecast         | `relay.mimecast.com` | dkim → dmarc → spf | verdict lives in `X-Mimecast-Spam-Score:`               |
| Zoho             | `mx.zohomail.com`    | dkim → spf → arc   | bare `dkim=pass`, no `header.d`                         |

</DataTable>

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.

<CodeBlock
  lang="email"
  filename="Vendor X-headers with their publishers' own decoding keys"
  code={vendorXHeaders}
/>

`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](https://learn.microsoft.com/en-us/defender-office-365/anti-spam-spam-confidence-level-scl-about)
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](https://learn.microsoft.com/en-us/defender-office-365/anti-spam-bulk-complaint-level-bcl-about)
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](https://mimecastsupport.zendesk.com/hc/en-us/articles/34000405766547-Spam-Phishing-Spam-Scanning)
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](https://community.cisco.com/t5/email-security/how-to-read-headers-for-cisco-email-security/td-p/3096135)
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](https://community.cisco.com/t5/email-security/can-we-block-spoofed-e-mail-coming-inbound/m-p/1451688/highlight/true)
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](https://learn.microsoft.com/en-us/answers/questions/745961/dkim-and-dmarc-failed-received-an-spoofed-email-fr)
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](https://learn.microsoft.com/en-us/answers/questions/54940/remove-header-exchange-online)
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.

<CTA
  title="See the ARC chain at every hop"
  description="Paste a forwarded message's headers into the free ARC chain analyzer to see the AAR, AMS and Seal at every hop, with cv= per instance — no signup."
  label="Open the ARC chain analyzer"
  href="/tools/arc-chain-analyzer/"
/>