The order I work in
- Did Exchange Online see the message at all? Message trace.
- What happened to it? Message trace details: delivered, failed, quarantined, filtered as spam, forwarded.
- Why? The headers of a delivered copy, or the NDR of a failed one.
- Which path did it take? Connectors, accepted domains, gateways, on-premises servers.
- What is the fix, and how do we test it? Write down cause, evidence, correction and test.
Step 1 and 2: message trace
The Exchange admin center has a message trace view, but PowerShell is faster to repeat and to share. Get-MessageTraceV2 replaces the older Get-MessageTrace; it searches the last 90 days, at most 10 days per query.
Connect-ExchangeOnline
# Everything from one sender to one recipient in the last two days
Get-MessageTraceV2 -SenderAddress "billing@vendor.example" -RecipientAddress "ap@example.com" `
-StartDate (Get-Date).AddDays(-2) -EndDate (Get-Date) |
Select-Object Received, SenderAddress, RecipientAddress, Subject, Status, FromIP, MessageTraceId
# The event history of one message
Get-MessageTraceDetailV2 -MessageTraceId "<MessageTraceId>" -RecipientAddress "ap@example.com" |
Select-Object Date, Event, Action, Detail
The Status already narrows it down: Delivered, Failed, Quarantined, FilteredAsSpam, Pending. If the trace shows nothing, the message never reached Exchange Online. Then look at the sender's side, at MX records, at a gateway in front, or at an on-premises server in a hybrid setup.
For "everything to this mailbox looks odd since Monday", group instead of reading line by line:
Get-MessageTraceV2 -RecipientAddress "ap@example.com" -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date) |
Group-Object Status, FromIP | Sort-Object Count -Descending | Select-Object Count, Name
Step 3: read the headers
For a delivered message, the headers say why it landed where it did. Copy them from Outlook (File, Properties) and paste them into the Message Header Analyzer of the Microsoft Remote Connectivity Analyzer, or read the key lines directly:
| Header | What it tells you |
|---|---|
Authentication-Results | SPF, DKIM and DMARC results, and compauth: Microsoft's composite verdict with a reason code |
X-Forefront-Antispam-Report | Spam confidence level (SCL), filter verdict (SFV) and category (CAT, for example SPOOF or PHSH) |
X-MS-Exchange-Organization-AuthAs | Internal, Partner or Anonymous: whether Exchange Online trusted the connection the message came in on |
Received lines | The path, from the bottom up, with host names and times |
ARC-* | Authentication results preserved by intermediaries such as gateways or mailing lists |
For a failed message, the NDR code points to the area: 5.1.10 recipient not found, 5.4.1 relay access denied or recipient not found during directory-based edge blocking, 5.4.14 hop count exceeded, usually a routing loop, 5.7.1 delivery not authorised, often a restriction or a transport rule.
Step 4: check the path
Get-AcceptedDomain | Format-Table DomainName, DomainType, Default
Get-InboundConnector | Format-List Name, Enabled, ConnectorType, SenderIPAddresses, TlsSenderCertificateName, RestrictDomainsToCertificate, EFSkipIPs, EFSkipLastIP
Get-OutboundConnector | Format-List Name, Enabled, ConnectorType, RecipientDomains, SmartHosts, TlsSettings, IsTransportRuleScoped
Accepted domain type matters: Authoritative means every recipient must exist in Exchange Online, InternalRelay means unknown recipients are routed on, for example to on-premises. A wrong type here explains many "some recipients work, some do not" cases.
Cases from projects, anonymised
Internal mail goes missing after a partial migration
Symptom: after part of the mailboxes moved to Exchange Online, mail between on-premises and cloud mailboxes disappeared, sporadically and not reproducibly. Cause: a connector that took precedence, combined with an outdated accepted domain. Fix: connector scope and accepted domain corrected, routing documented, a runbook for the team. The point: the cause was provable in the trace and the connector configuration, not just plausible.
Internal mail suddenly treated as external
A typical pattern: after the certificate on the on-premises hybrid server is renewed, internal mail arrives in Exchange Online with external tags and higher spam scores. In the headers, AuthAs says Anonymous. Cause: the inbound connector still expects the old certificate name in TlsSenderCertificateName. Fix: update the connector or rerun the Hybrid Configuration Wizard, then check AuthAs on a new test message.
Newsletters land in junk after DMARC is tightened
Symptom: after moving to p=quarantine, newsletters to customers landed in junk. Cause: the newsletter tool used its own Return-Path and its own DKIM domain, so neither SPF nor DKIM was aligned. In one project, three undocumented systems sent in the domain's name this way. Fix: custom sending domain and DKIM in each tool, then the next DMARC step. See DMARC to p=reject.
Everything fails SPF behind a gateway
Symptom: with a third-party gateway in front of Exchange Online, Microsoft's filter flags legitimate mail as spoofed. Cause: Exchange Online sees the gateway's IP as the source, so SPF fails for every sender. Fix: Enhanced Filtering for Connectors on the inbound connector, so Exchange Online evaluates the original source, and an inbound connector restricted to the gateway's IPs or certificate so nobody can bypass the gateway.
Checklist
- Message trace first: did Exchange Online see the message?
- Trace details read: which event, which action, which detail?
- Headers of a delivered copy analysed: authentication, SCL, AuthAs, path
- NDR code mapped to an area
- Accepted domains and connectors checked against the intended path
- Cause, evidence, correction and test written down
- Runbook updated, so the next person starts at step 1, not at a guess