Type to search 66 articles.

    Practical engineering guidance

    Tracing a message through Exchange Online mail flow

    Message trace answers where a message went. Getting a useful answer depends on knowing which tool covers which window, and on reading the event sequence rather than the final status.

    Series: Exchange and Microsoft 365

    • Exchange Online
    • Mail flow
    • Troubleshooting

    “The email never arrived” is one of the least specific problems anyone will bring you, and one of the most answerable. Exchange Online records what it did with every message, and the record is good. The difficulty is that people read the final status, conclude “delivered”, and stop — when the interesting information is in the sequence of events and the location it was delivered to.

    The symptom, and the four questions that scope it

    Before opening any tool, establish four facts. Each one changes which tool you use.

    1. Exact sender and recipient addresses. Not “from the finance system” — the actual envelope addresses, which are frequently not the addresses people see.
    2. When, within an hour. This decides which tool has the data.
    3. Direction. Inbound from outside, outbound, or internal. These traverse different paths and fail differently.
    4. Is this one message or a pattern? One message is a trace. A pattern is a configuration problem, and tracing individual messages will waste your afternoon.

    The architecture a message passes through

    An inbound internet message reaches Exchange Online Protection first: connection filtering, then anti-malware and anti-spam evaluation, then mail flow rules, then policy evaluation, then delivery to the mailbox — where inbox rules and retention policies can move it again. An outbound message runs a comparable path in reverse, with outbound spam protection and connector selection.

    Two implications matter for diagnosis:

    • “Delivered” means delivered to the mailbox, not visible in the inbox. An inbox rule, a junk classification, a retention policy or a client-side rule can move a delivered message. This is the single most common false trail.
    • A message can be acted on at several points. The final status hides the sequence, and the sequence is where the cause is.

    Choosing the right tool for the window

    This is where people waste the most time, so it is worth being precise.

    Tool Window Use for
    Message trace in the admin centre Recent activity, summary view Quick answers, one or two messages
    Get-MessageTraceV2 Up to 90 days, 10 days per query Scripted or bulk investigation
    Get-MessageTraceDetailV2 Per-message event detail Why something happened
    Historical search Long-window, asynchronous report Older messages, audit requests

    Get-MessageTraceV2 replaced Get-MessageTrace, which Microsoft began deprecating in September 2025. If your saved scripts still call the original cmdlet, they are on borrowed time — this is worth checking now rather than when a report stops running.

    The V2 cmdlet has specific limits that shape how you query it:

    • It searches up to 90 days of data.
    • A single query can return no more than 10 days worth of data, so a 90-day investigation is a loop over windows, not one call.
    • Run with no parameters it returns only the last 48 hours, which is a common cause of “there is no record of it” when the message is three days old.
    • It returns 1000 results by default, 5000 maximum, so a broad query silently truncates.
    • It needs Exchange Online PowerShell V3 module version 3.7.0 or later.

    Diagnosis: the commands

    Connect first, then work from specific to general.

    Connect-ExchangeOnline
    
    # Always state the window explicitly. The default is 48 hours and will mislead you.
    $start = (Get-Date).AddDays(-5)
    $end   = Get-Date
    
    Get-MessageTraceV2 -StartDate $start -EndDate $end `
        -SenderAddress 'sender@fabrikam.com' `
        -RecipientAddress 'recipient@contoso.com' |
        Select-Object Received, SenderAddress, RecipientAddress, Subject, Status, MessageId |
        Sort-Object Received

    Read the Status column, and treat each value as a different investigation:

    • Delivered — it reached the mailbox. The problem is now a mailbox-side one: inbox rules, junk folder, retention, or a client-side rule.
    • Failed — it was rejected. The detail carries the reason and usually an enhanced status code.
    • Pending — delivery is being retried. Common for a recipient system that is unavailable.
    • Quarantined — policy held it. Look in quarantine and establish which policy.
    • Filtered as spam — it was classified. Check the spam confidence level in the detail.
    • Expanded — it went to a distribution group and fanned out. Trace the individual members rather than the group, or you will conclude nothing happened.

    Then get the event sequence for the specific message:

    $trace = Get-MessageTraceV2 -StartDate $start -EndDate $end `
        -RecipientAddress 'recipient@contoso.com' -Status Delivered |
        Select-Object -First 1
    
    Get-MessageTraceDetailV2 -MessageTraceId $trace.MessageTraceId `
        -RecipientAddress $trace.RecipientAddress |
        Select-Object Date, Event, Detail |
        Sort-Object Date

    The Event sequence is the real answer. RECEIVE, TRANSFER, DELIVER, SEND, and the policy events between them tell you what acted on the message and in what order. A mail flow rule that rewrote a recipient, a connector that routed it somewhere unexpected, or a policy that redirected it will be visible here and nowhere else.

    For a message older than 90 days, use historical search instead. It runs asynchronously and returns a report rather than an interactive result, which is the correct tool for an audit request and the wrong one for someone waiting on the phone.

    Interpreting the common cases

    Delivered but not in the inbox. Check the junk folder, then inbox rules — including rules the user forgot, and rules created by a compromised session. A forwarding rule to an external address is a strong compromise indicator and should change the nature of the investigation immediately.

    Failed with a 5.x.x code. The enhanced status code names the cause. A recipient-side rejection needs the recipient’s administrator; there is a limit to what you can establish from your side, and it is reached quickly.

    Filtered as spam on outbound. Your own message was classified leaving the tenant. Usually this means the sending domain’s authentication is incomplete — see SPF, DKIM and DMARC as an operational sequence, which is the underlying fix rather than a per-message workaround.

    No record at all. Three possibilities, in order of likelihood: the window was wrong (the 48-hour default), the address was wrong (an alias rather than the envelope address), or the message never reached Exchange Online — which points at the sender’s system or at DNS.

    Delivered to an unexpected mailbox. Look for a mail flow rule or a forwarding configuration. Both are legitimate features and both are also used by attackers, so establish who created it and when.

    Resolution and validation

    The resolution depends entirely on what the trace showed, and the trace usually makes it obvious. What is worth insisting on is the validation step, because mail flow changes are easy to half-fix.

    After any change — a mail flow rule, a connector, a policy exception — send a test message that exercises the same path and trace it. Confirm the event sequence now shows what you expect, not merely that the status says delivered. Then confirm the original sender can reproduce a successful send, because your test from inside the tenant may not traverse the same path as theirs from outside.

    For a pattern rather than a single message, confirm the fix across a sample of senders and recipients over a full day before closing it.

    Operational notes

    Do not paste trace output into a ticket or a vendor portal. It contains addresses and subjects, which are personal data and frequently sensitive. Summarise, or share through an appropriate channel.

    Build the 10-day loop once. If you regularly investigate across the full 90 days, write the windowing loop properly and reuse it rather than running a truncated query and drawing conclusions from the first 1000 results.

    Check your existing scripts for Get-MessageTrace. Any reporting that still calls the original cmdlet needs migrating to V2.

    Verification and limits

    The Get-MessageTraceV2 and Get-MessageTraceDetailV2 cmdlets, the 90-day retention, the 10-day per-query limit, the 48-hour default, the 1000/5000 result limits, the 3.7.0 module requirement and the deprecation of Get-MessageTrace from September 2025 were checked against current Microsoft documentation on 20 September 2026.

    No query was executed against a tenant for this article. Message trace is read-only and safe to run; the cautions are about interpretation and about handling the output, which contains personal data. Retention windows and limits change — confirm them before building reporting that depends on them.

    References