The FBI’s Internet Crime Complaint Center logged 1,008,597 complaints in 2025 and $20.877 billion in reported losses, a 26% jump year over year. Phishing and spoofing were the single most-reported crime type, with 191,561 complaints and $215.8 million in losses. Business Email Compromise added another $3,046,598,558 across 24,768 complaints. Stack the categories that abuse email trust together and the reported losses run past $3 billion. Nearly all of it starts with a message that claims to be from someone it is not.

When a user forwards you a suspicious email, the body and the branding lie to you by design. The raw headers do not. They are the ground truth of how the message actually moved across the internet, which servers touched it, and whether the sending infrastructure was authorized to speak for the domain in the visible From:. This post walks the full chain: reading Received: hops bottom-up, separating the envelope identity from the header identity, and reading SPF, DKIM, and DMARC well enough to render a verdict instead of a guess.

Anatomy of a Header Block

Every message carries two categories of headers, and the distinction decides how much you trust each one.

Sender-supplied headers are set by whoever composed and injected the message. These include From:, To:, Subject:, Reply-To:, Message-ID:, Date:, and any X- headers the sending client added. An attacker controls all of these completely. They are evidence, not proof.

Infrastructure-added headers are stamped by mail servers as the message transits. Each Received: line is added by a Mail Transfer Agent (MTA). Critically, the Authentication-Results: header is added by the receiving boundary MTA, your inbound gateway, Gmail, or Microsoft 365. Because that header is written by infrastructure you trust after it evaluated the message, it is the most trustworthy line in the block, provided it actually came from your gateway and not from lower in the stack where an attacker could forge a fake one.

The governing rule of header analysis: trust flows top-down from your own boundary. Everything a server you control stamped is reliable. Everything below the first hop you recognize is attacker-influenced and must be corroborated.

Reading Received Headers Bottom-Up

Each MTA that handles a message prepends a single Received: header at the top of the block before passing it on. The message accumulates a stack in reverse chronological order. The bottom-most Received: line is the first hop, the origin. The top-most is the final delivery MTA that dropped the message in the mailbox. You read them bottom-up to reconstruct the journey.

A well-formed Received: line has a predictable grammar:

Received: from <sending host / HELO name> (<reverse-DNS> [<IP>])
          by <receiving host>
          with <protocol, e.g. ESMTPS>
          id <queue id>;
          <timestamp>

The from clause names the host that connected and the HELO/EHLO string it announced. The by clause is the server that accepted the connection. The with clause is the protocol (ESMTPS indicates TLS was used). The timestamp is when that server handled it.

Finding the True Originating IP

The genuine source is almost always in the bottom-most Received: line, in the from clause’s bracketed IP. Where present, X-Originating-IP: records the actual submitting client’s IP, which is even more direct. But note the provider caveat: Gmail, Outlook.com, Yahoo, and ProtonMail strip X-Originating-IP, while some corporate systems and Fastmail preserve it. If it is absent, fall back to the bottom trusted Received: hop.

The trap is that an attacker can inject forged Received: lines below the first legitimate hop to fabricate a plausible-looking origin. Those fake lines sit at the bottom of the stack. You defeat this by anchoring on the lowest line you can attribute to a server you recognize (your gateway, or a known reputable provider), and treating everything below it as unverified narrative the attacker may have authored.

Computing Hop Times

Every Received: line is timestamped, so you can measure how long the message sat at each hop. Subtract the timestamp on a line from the timestamp on the line above it (the next hop chronologically, since you read upward). Normalize everything to UTC first so timezone offsets don’t fool you.

Worked example from a three-hop message:

Hop 1 (bottom / origin):   09:14:02 +0000
Hop 2 (relay):             09:14:05 +0000   -> delta 3s      (normal)
Hop 3 (delivery):          09:19:47 +0000   -> delta 5m 42s  (flag)

A sub-second-to-few-seconds gap is routine. A gap of several minutes suggests spam scanning, greylisting, or a queue backlog, worth noting but often benign. Gaps of hours, or timestamps that move backward as you climb the stack (“time travel”), signal either significant clock skew or forged/injected Received: lines. Small negative or zero deltas are usually just benign clock skew between servers running slightly off UTC, not tampering. Routing through an unexpected country between two hops that should be adjacent is a strong anomaly.

The Identity Fields Spoofers Exploit

Email has two completely separate “from” identities, and understanding this split is the entire game.

The canonical spoof keeps a valid envelope domain the attacker actually controls (so SPF passes cleanly) while forging the visible From: to a trusted brand. SPF passes. The victim sees the brand. Only DMARC alignment catches the mismatch.

Two more sender-supplied tells: the Message-ID: domain should normally match the sending or From: domain, and legitimate providers use predictable formats. A mismatched, malformed, or missing Message-ID is a spoofing indicator. X-Mailer: or User-Agent: strings that name bulk-mailing tools rather than a real client, and provider-specific X-Spam-* scores, round out the triage picture.

SPF: Authorizing the Envelope

Sender Policy Framework (RFC 7208, Proposed Standard, April 2014) answers one narrow question: is this sending IP authorized to send mail for the envelope (RFC5321.MailFrom) domain? The receiver takes the envelope domain, looks up its SPF record in a DNS TXT lookup, and checks whether the connecting IP is listed. SPF can also authorize the HELO identity, and RFC 7208 recommends checking HELO before MAIL FROM when both are evaluated.

Results you will see: pass, fail (hard -all), softfail (~all, suspicious but accept), neutral (?all), plus none/permerror/temperror. The limitation is the whole reason DMARC exists:

SPF=pass means only that the sending IP is authorized for the envelope domain. It says nothing about the visible From:. An attacker who owns mail.attacker-infra.com and publishes SPF for it gets SPF=pass while forging any From: they like.

DKIM: Signing the Message

DomainKeys Identified Mail (RFC 6376, September 2011) attaches a cryptographic signature that proves a message was authorized by a domain and that specific headers plus the body were not altered in transit. The signature lives in the DKIM-Signature: header. Read the tags:

DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
    d=yourbank.com; s=selector1;
    h=from:to:subject:date;
    bh=<base64 body hash>;
    b=<base64 signature>

Verification is five steps: extract the DKIM-Signature; fetch the public key from DNS via s= and d=; canonicalize per c=; recompute the body hash and compare it to bh=; then verify b= against the public key over the signed headers. If it all checks out, DKIM passes.

Why DKIM=pass Is Necessary But Not Sufficient

A valid signature only helps DMARC if d= is aligned with the From: domain. A spoof either omits DKIM (yielding dkim=none) or signs with d=attacker-infra.com, which produces a technically valid signature that is not aligned with the forged brand From:.

The April 2025 Google DKIM-replay campaign made this concrete. Attackers captured a legitimately DKIM-signed email generated by Google (sent from a no-reply@google.com security alert) and re-blasted it to many recipients. Because the original signature stayed valid, receivers saw DKIM=pass, and DMARC passed via DKIM, even though the mail was delivered from rogue infrastructure abusing a Google OAuth app name and Google Sites to stage credential-harvesting lures (reported by The Hacker News and BleepingComputer). The lesson: DKIM=pass proves a domain signed the bytes, not that this delivery is legitimate. Leaving deprecated selectors and keys published in DNS forever leaves you open to replay.

DMARC: Alignment Is the Linchpin

DMARC ties SPF and DKIM to the identity the user actually sees. It does not merely ask “did SPF or DKIM pass?” It asks: did at least one of them pass and is the domain it authenticated aligned with the visible RFC5322.From domain?

Alignment modes are controlled by aspf (for SPF) and adkim (for DKIM), and both default to relaxed (r):

Put the spoof through this machine: the envelope domain mail.attacker-infra.com sends real mail with valid SPF, so SPF=pass. But the From: is forged to yourbank.com. SPF is authenticated for attacker-infra.com, which shares no organizational domain with yourbank.com, so aspf alignment fails. There is no DKIM signature to rescue it. No aligned pass exists, so DMARC fails. What happens next is governed by the domain’s published policy tag p=: none (monitor only), quarantine (spam folder), or reject (bounce at the gateway).

Currency note (2026): The original DMARC specification, RFC 7489, was an Informational document from March 2015. In May 2026, DMARCbis shipped as three Standards-Track RFCs: RFC 9989 (core protocol), RFC 9990 (aggregate reporting), and RFC 9991 (failure reporting). RFC 9989 obsoletes RFC 7489 and RFC 9091 and promotes DMARC to Proposed Standard for the first time. Practically, v=DMARC1 records written against 7489 remain backward-compatible, so your existing analysis skills carry over. Cite RFC 9989 as the current authority.

Authentication-Results, Decoded

The receiving boundary MTA condenses all three checks into one header, and it is the fastest read in the whole block:

Authentication-Results: mx.google.com;
    spf=pass (google.com: domain of bounce@mail.attacker-infra.com
        designates 203.0.113.44 as permitted sender)
        smtp.mailfrom=bounce@mail.attacker-infra.com;
    dkim=none;
    dmarc=fail (p=REJECT sp=REJECT dis=NONE) header.from=yourbank.com

Read it left to right per mechanism: spf=pass with smtp.mailfrom= tells you which envelope domain passed. dkim= with header.d= tells you which domain signed. dmarc= reports the aligned verdict with the evaluated policy and shows header.from=, the visible domain it aligned against. Here, SPF passed for the attacker’s envelope domain, no DKIM exists, and DMARC failed because neither aligned with header.from=yourbank.com. Trust this header only when it came from your own inbound gateway; attackers can plant a forged Authentication-Results: line lower in the stack, so verify which server stamped it.

ARC and the Forwarding Problem

Legitimate forwarding and mailing lists break SPF (the relay’s IP isn’t authorized) and can break DKIM (the list mutates the body). The Authenticated Received Chain (ARC, RFC 8617, Experimental, July 2019) lets each intermediary preserve the original authentication results across three headers, ARC-Authentication-Results (AAR), ARC-Message-Signature (AMS), and ARC-Seal (AS), so a trusted receiver can choose to override a DMARC failure caused by known-good forwarding. When you see a DMARC fail on a message that came through a mailing list, check the ARC chain before condemning it.

Worked Example: SPF Pass, DMARC Fail

Here is a realistic raw block for a spoofed helpdesk phish. Read it bottom-up.

Delivered-To: victim@example.com
Received: from mx-in.example.com (mx-in.example.com [198.51.100.10])
        by inbox.example.com with ESMTPS id 9f2a1;
        Tue, 30 Jun 2026 09:19:47 +0000
Received: from mail.attacker-infra.com (mail.attacker-infra.com [203.0.113.44])
        by mx-in.example.com with ESMTPS id 771cd;
        Tue, 30 Jun 2026 09:14:05 +0000
Received: from unknown (HELO mail.attacker-infra.com) (203.0.113.44)
        by mail.attacker-infra.com with ESMTP id 001aa;
        Tue, 30 Jun 2026 09:14:02 +0000
Authentication-Results: mx-in.example.com;
        spf=pass (mx-in.example.com: domain of bounce@mail.attacker-infra.com
            designates 203.0.113.44 as permitted sender)
            smtp.mailfrom=bounce@mail.attacker-infra.com;
        dkim=none;
        dmarc=fail (p=REJECT sp=REJECT dis=NONE) header.from=yourbank.com
Return-Path: <bounce@mail.attacker-infra.com>
Message-ID: <20260630091402.001aa@mail.attacker-infra.com>
Reply-To: IT Support <helpdesk@attacker-infra.com>
From: IT Support <helpdesk@yourbank.com>
To: victim@example.com
Subject: Action required: verify your account credentials
X-Mailer: PowerMTA/5.0b1

Step 1 — Read Authentication-Results from the trusted gateway. It was stamped by mx-in.example.com, our own inbound MTA, so we trust it. Verdict summary: spf=pass, dkim=none, dmarc=fail.

Step 2 — Reconcile the identities. Return-Path (envelope) is bounce@mail.attacker-infra.com. The visible From: is helpdesk@yourbank.com. Those are different organizational domains. SPF authenticated the envelope domain attacker-infra.com and passed, exactly as designed, but that domain does not align with yourbank.com. No DKIM signature exists to provide an aligned pass. Therefore DMARC fails, and the bank’s policy is p=reject.

Step 3 — Note the Reply-To divergence. Replies would go to helpdesk@attacker-infra.com, not the bank. Classic response-capture.

Step 4 — Trace Received bottom-up. The origin is 203.0.113.44 announcing HELO mail.attacker-infra.com. The two lower lines were written by attacker infrastructure; the top line, written by our mx-in, confirms the same IP handed the message to us. The true origin is 203.0.113.44.

Step 5 — Compute hop times. Bottom to next: 09:14:02 to 09:14:05 = 3s (normal). Next to delivery: 09:14:05 to 09:19:47 = 5m 42s (flag; the message sat somewhere, consistent with scanning or a suspicious relay).

Step 6 — Corroborating tells. The Message-ID domain is attacker-infra.com, not yourbank.com. The X-Mailer is a bulk-mail engine, not a human’s client. Every independent signal agrees.

Verdict: confirmed spoof of yourbank.com. SPF passing on the attacker’s own domain is not exculpatory; it is precisely the mechanic of the attack. The next move is to threat-intel 203.0.113.44 and attacker-infra.com against your reputation feeds before you close the ticket.

Red Flags Reference

Triage Checklist

  1. Pull the full raw headers (not the pretty view). “Show original” in Gmail, message source in most clients.
  2. Read Authentication-Results first, and confirm it was stamped by your own trusted gateway. Record spf/dkim/dmarc.
  3. Compare the three identities: visible From: vs Return-Path (envelope) vs DKIM d=. Any divergence is your lead.
  4. Read Received: bottom-up and extract the true origin IP (or X-Originating-IP if preserved).
  5. Compute hop times; flag multi-minute gaps, time-travel, or unexpected geographic routing.
  6. Check Message-ID domain and X- headers (X-Mailer, spam scores) for corroboration.
  7. Geolocate and threat-intel the origin IP and any suspect domains against reputation feeds.
  8. Render the verdict, then contain or escalate: block the sender/IP, quarantine matching messages, and open a BEC/phishing case if a user interacted.

Why is authentication now near-ubiquitous enough that a modern spoof usually can’t forge a well-run brand’s From: cleanly? Two forces. CISA’s Binding Operational Directive 18-01 mandates p=reject DMARC for US federal agencies. And the Google/Yahoo bulk-sender rules (announced October 2023, enforced from February 2024) require anyone sending more than roughly 5,000 messages a day to personal accounts to deploy SPF and DKIM, publish DMARC at a minimum p=none, align the From: domain with SPF or DKIM, keep spam complaints under 0.1%, and offer one-click List-Unsubscribe (RFC 8058) by June 1, 2024. That is why alignment, not a bare SPF pass, is the operative check today.

// try it live

Paste a raw header block and get the Received hop chain, envelope-vs-header identities, and SPF/DKIM/DMARC alignment broken down automatically.

EMAIL HEADER PARSER →

Key Takeaways

References

  1. 2025 IC3 Annual Report (FBI Internet Crime Complaint Center)
  2. FBI IC3 2025 report: Email fraud phishing, spoofing, and BEC breakdown (Red Sift)
  3. Phishers Exploit Google Sites and DKIM Replay to Send Signed Emails (The Hacker News)
  4. Phishers abuse Google OAuth to spoof Google in DKIM replay attack (BleepingComputer)
  5. RFC 9989: Domain-Based Message Authentication, Reporting, and Conformance (DMARC)
  6. RFC 7208: Sender Policy Framework (SPF), Version 1
  7. RFC 6376: DomainKeys Identified Mail (DKIM) Signatures
  8. RFC 8617: The Authenticated Received Chain (ARC) Protocol
  9. RFC 8058: Signaling One-Click Functionality for List Email Headers
  10. Google & Yahoo Email Authentication Requirements for Bulk Senders (Valimail)
  11. DMARCbis is Now Official: RFC 9989, 9990, and 9991 Replace RFC 7489 (Security Boulevard)
« AI-Driven Cyber Threat Attribution: Why… Anatomy of a Ransomware Attack:… »