Back to Blog

Email Analysis: Dissecting a Phishing Email.

September 1, 2026 · Yomal Praveen
Email AnalysisPhishingSOCDFIR

Phishing triage is a daily part of my job. Someone reports a suspicious email, a ticket lands in the queue, and I have a few minutes to decide whether it gets purged from every mailbox in the company or handed back with a note saying it's fine. After a few hundred of these, the process settles into a routine. This post is that routine written down.

Two rules before anything else, because they matter more than any technique below. Never click the link and never open the attachment. All of the analysis here happens on headers, hashes and sandboxes, not on the live payload. And every indicator in this post is defanged: hxxps:// instead of https://, and evil[.]com instead of the clickable version. Copy that habit. A live URL pasted into a ticket becomes a clickable link for whoever reads it next. And every brand, domain and address in the examples here is made up.

Reading the headers

The body of an email is what the sender wants you to see. The headers are what the mail servers wrote down while passing it along, and servers are worse liars than people. Every client can show them: Gmail calls it Show original, Outlook calls it View message source. That raw text is where the analysis starts.

From proves nothing. It's a text field the sender fills in, the same way you can write any name you like on the back of an envelope. Every spoofed email has a convincing From line. That's the whole point of spoofing.

Return-Path is more honest. During the SMTP handshake the sending server declares an address for bounces, called the envelope sender, and your receiving server writes it into the message on delivery. The reader never sees this field, so attackers often don't bother making it pretty. When From says service@acmepay.com and Return-Path says x9k2@bounce.secure-notify[.]top, the mail is telling on itself. One caveat from real triage: bulk senders like SendGrid and Mailchimp legitimately use their own bounce domains, so treat a mismatch as a lead, not a conviction.

Reply-To decides where your reply actually goes. The classic business email compromise pattern is a director's name in From and a free Gmail address in Reply-To, so the conversation quietly moves into the attacker's mailbox. Newsletters and CRM platforms use Reply-To legitimately, which is why I look at the pair: an executive display name plus a freemail reply address is a strong signal on its own.

Received headers are the travel log. Every server that handles the message adds one to the top, so you read them bottom to top. Here is the part nobody tells beginners: the sender controls everything below the moment the message reached your infrastructure, and attackers do insert fake Received lines to look respectable. The only hops you can trust are the ones your own servers wrote, and the connecting IP recorded at that handoff is the honest answer to where the email really came from.

Message-ID is a unique tag stamped by the sending system, and the part after the @ normally matches the sending infrastructure. Mail claiming to be from AcmePay with a Message-ID minted on srv2.secure-notify[.]top has some explaining to do. It's forgeable, so it stays supporting evidence, never the verdict.

Authentication-Results is where your own gateway records the SPF, DKIM and DMARC verdicts. Note the wording: your own gateway. Attackers sometimes pre-insert a fake copy of this header full of reassuring pass results, which is exactly why gateways strip inbound copies that claim to be theirs. Trust the one your infrastructure stamped and ignore any others.

Here's what a bad day looks like in seven lines:

Authentication-Results: spf=pass (sender IP is 203.0.113.11)
  smtp.mailfrom=bounce.secure-notify.top; dkim=none;
  dmarc=fail header.from=acmepay.com
From: "AcmePay Support" <service@acmepay.com>
Reply-To: <billing.desk.verify@gmail.com>
Return-Path: <x9k2@bounce.secure-notify.top>
Message-ID: <20260901071542.9A3F1B@srv2.secure-notify.top>

Four problems in one glance: SPF passed, but for the attacker's own bounce domain, not AcmePay. DMARC failed against the domain the reader actually sees. Replies go to a Gmail address. And the Message-ID was minted on the attacker's server. Any one of these is worth a closer look. All four together is a verdict.

SPF, DKIM and DMARC, without the acronym soup

These three verdicts sit in every Authentication-Results header and confuse people endlessly, so here is the version I give non-technical colleagues.

SPF is a guest list. A domain publishes the list of servers allowed to send mail on its behalf. The list lives in DNS, the internet's public directory, so anyone can look it up. When a message arrives, the receiving server checks whether the server that delivered it is on the list. On the list, pass. Not on the list, fail. The trap: SPF checks the Return-Path domain, not the From address a human reads. That's how the AcmePay fake above earned its spf=pass. The attacker's own domain vouched for the attacker's own server, which is technically true and completely meaningless.

A real SPF record is one line of DNS:

v=spf1 include:_spf.google.com ~all

Read it as "Google's servers may send for me, and everything else gets a soft no". That squiggle at the end matters more than you'd think.

The ~all versus -all question. The record ends with a symbol that tells receivers what to do about every server that isn't on the list. There are four options:

QualifierResultWhat the receiver does
+passServer is authorized
?neutralDomain takes no position, rare in practice
~softfail"Probably not authorized." Accept, but hold it against the message in spam scoring
-fail"Not authorized." Receivers may refuse the message outright

So ~all is a soft no and -all is a hard no. You would expect everyone to run the hard no, and yet most domains use ~all. The reason is forwarding. A forwarded message arrives from the forwarding server's address, which the original domain never listed, and a strict -all would get real mail rejected. Domains soften the SPF answer instead and let DMARC, which I'll get to in a moment, make the final call.

DKIM is a tamper seal. The sending server stamps each message with a seal only it can produce, and publishes the pattern for checking that seal where any receiving server can look it up. The seal covers the important headers and the body, so if anything changes in transit, the seal breaks. For the technical readers: the seal is a signature made with a private key, and the public key sits in DNS at <selector>._domainkey.<domain>. Two things DKIM does not prove: that the mail is safe, or that the visible From is real. An attacker can seal flawlessly with their own domain. dkim=pass only means "this domain signed it and nobody has edited it since".

DMARC ties the two together. It's a policy the domain owner publishes, and it reads like a standing order: "if a message claims to be from us in the From header, then SPF or DKIM must pass for our domain, not somebody else's, and here is what to do when neither does." The options are p=none (deliver it, just report it), p=quarantine (spam folder) and p=reject (refuse it). DMARC is the only one of the three that looks at the From address a human actually sees, which makes dmarc=fail the single most damning line you'll find in a header.

Now the uncomfortable part: all three can pass on a phish. A compromised mailbox sends through real infrastructure with real keys, and a lookalike domain registered yesterday carries its own perfectly valid SPF, DKIM and DMARC. A display name costs nothing. Authentication verifies the envelope, not the intent, and I regularly mark emails as malicious that pass every check on this page.

The easy way to read headers

You don't have to eyeball raw headers every time. Copy them into the MxToolbox Email Header Analyzer and it lays out every relay hop with timing, then pulls the SPF, DKIM and DMARC results into a table you can read in ten seconds. It's the tool I point new analysts at first, and the quick second opinion I still use myself.

Two alternatives worth knowing: Google's Messageheader draws a clean hop-by-hop view of where delivery slowed down, and Microsoft's Message Header Analyzer is the best of the three at decoding the antispam headers Microsoft 365 stuffs into every message.

One warning applies to all of them: headers carry your internal hostnames, IPs and the recipient's address. Trim anything sensitive before pasting into a third-party site. These tools need the headers only, never the body.

The body and the links

Headers come first because they're objective, but the body is where you can feel the con. It's the same handful of levers in ticket after ticket. A generic greeting, "Dear Customer", from a company that bills you by name. Manufactured urgency: a 24-hour deadline, a suspended account, a missed parcel. And a request no legitimate company makes over email. Nobody real asks for your password or MFA code. Nobody real asks to be paid in gift cards.

Links get the closest look. The text of a link and its destination are completely independent, so a link reading acmepay.com/verify can point anywhere. Hover on desktop, long-press on mobile, and then read the actual domain from the end, right to left. There's a diagram of this trick in the next section, because it fools more people than any other.

Shorteners and open redirects are the same idea one layer deeper: the chain starts on a reputable domain and ends somewhere ugly, so judge the final destination, never the first hop. urlscan.io will follow the chain and screenshot the landing page without you ever touching it. Set the scan visibility to unlisted for anything work-related. Public scans are searchable by anyone, including the attacker, who may be watching for their own URLs. For decoding, extracting and defanging in bulk, CyberChef runs entirely in your browser and uploads nothing.

QR codes are the same trick in image form. Mail filters have only recently learned to decode them and still miss plenty, and the scan moves the click to a phone outside your company's controls.

Attachments: hash first, open never

The order matters. Hash the file first, Get-FileHash on Windows or sha256sum on Linux, then search that hash on VirusTotal. A hash search sends nothing. Uploading is different: submitted files are shared with AV vendors and downloadable by paying customers, so uploading a company invoice is a small data breach you performed on yourself, and it can tip off an attacker watching for their sample. If the hash is unknown and I need to see behavior, the file goes to a sandbox like ANY.RUN, never to my own machine. Keep in mind that free sandbox tiers make submissions public too. Same rule as urlscan.

Extensions that make me sit up straighter: .html and .htm, which are either a fake login page delivered as a file or HTML smuggling, where script in the attachment assembles the real payload in the browser so the gateway only ever scanned harmless-looking HTML. Then .iso and .img disk images, .lnk shortcuts inside ZIPs, the macro formats .docm and .xlsm, and the old classic double extension, invoice.pdf.exe, with Windows helpfully hiding the second half.

And a clean VirusTotal result proves only that the file isn't already known to be malicious. Fresh payloads start the day at zero detections.

Phishing vs a normal email, side by side

Everything above condenses into one principle: legitimate email is consistent. The sender's domain, the greeting, the tone, where the links go and what the message asks you to do all agree with each other. Phishing always breaks somewhere, because the sender isn't who the mail claims to be.

PhishingLegitimateFrom: AcmePay Support<service@secure-notify.top>Subject: URGENT: account will be closedDear Customer,We detected unusual activity on youraccount. Verify your identity within 24hours or it will be permanently closed.https://www.acmepay.com/verifyreally goes to: account-verify.netinvoice.htmlAcmePay Security Team123456From: AcmePay<service@acmepay.com>Subject: Your September statementHello Yomal,Your September statement is nowavailable in your account overview.No action is needed.https://www.acmepay.com/statementsgoes to: acmepay.comAnna PereraAcmePay Customer Servicehelp.acmepay.com
  1. The display name says AcmePay. The address is on a domain AcmePay has never heard of.
  2. Panic in the subject line. Real statements don't shout.
  3. A generic greeting from a company that knows your name.
  4. A deadline and a threat, there to make you act before you think.
  5. The link text and its real destination disagree.
  6. An attachment nobody asked for, in a risky format.

Look closely at the domains themselves, because lookalikes are better than most people think. A 1 swapped in for a lowercase l. acrnepay[.]com, where r and n sit close enough together to read as m. Cyrillic letters that render identically to Latin ones. And cousin domains like acmepay-security-alerts[.]com, which technically impersonate nothing: registered normally, carrying their own valid SPF, DKIM and DMARC, passing every authentication check while never touching AcmePay's actual domain. And anything to the left of a registered domain is a subdomain, a prefix the owner can name however they like, which is how acmepay.com ends up decorating an attacker's address. This is why reading from the end is the habit worth building:

read from the endhxxps://acmepay.com.account-verify.net/loginjust a subdomain,named to look like the brandthe site you are actually on,registered by the attacker

One more, since most email is read on phones now: mobile clients usually show only the display name and hide the address entirely. On a phone, "AcmePay Support" is all anyone sees. The real address, service@secure-notify[.]top, is one tap away, and almost nobody makes that tap.

Making the call, and what happens after

The ticket leads with the verdict: phishing, spearphishing, legitimate, or occasionally inconclusive. Evidence goes in order of strength. Authentication and header findings first, then infrastructure (a domain registered nine days ago on a hosting provider that shows up in half our tickets), then the payload, then the wording of the body. What the email says is the weakest evidence. What the servers recorded is the strongest.

The indicators go into a table, defanged. This campaign ran two lures, the spoofed AcmePay header above and the lookalike sender from the diagram, so both end up in the same table:

TypeIndicatorContext
Domainbounce.secure-notify[.]topReturn-Path / SPF domain
IP203.0.113[.]11Delivering server
URLhxxps://acmepay.com.account-verify[.]net/loginCredential harvesting page
Emailbilling.desk.verify@gmail[.]comReply-To
SHA-256a1b2c3... (attachment)invoice.html

One reported email is rarely one email, and this is where triage turns into analysis. I pivot in the SIEM (Stellar Cyber, in my case) on the sender domain, the source IP, the subject line and the attachment hash. Who else received this campaign, and did anyone click? The campaign is purged from every mailbox, the indicators blocked at the gateway, and anyone who clicked gets a credential reset and a conversation. The person who reported it gets a thank you, because reporting is the behavior you want more of.

The checklist

The whole post in one box, in the order I actually work:

  1. Get the raw email as a .eml file. Click nothing.
  2. From vs Return-Path vs Reply-To: do the domains agree?
  3. Received chain, bottom to top: which IP handed it to your infrastructure?
  4. Authentication-Results: SPF, DKIM, and above all DMARC against the From domain.
  5. Short on time? Paste the headers into MxToolbox.
  6. Links: real destination, read right to left, unwrap shorteners.
  7. Attachments: SHA-256, search VirusTotal, sandbox if unknown. Never open.
  8. Weigh it: headers beat infrastructure, infrastructure beats payload, payload beats prose.
  9. Write the verdict, defang the IOCs, close the ticket.
  10. Pivot: find every other recipient, purge, block, reset whoever clicked.

Most of this is learnable in a week of triage. The discipline of never clicking is the part you keep forever.