Search by sender and recipient

The default search: every event for a sender/recipient pair in the chosen period, sorted chronologically.

Letzte 48 Stunden

Exchange on-prem

Get-MessageTrackingLog (Exchange) Built-in

Get-TransportService | Get-MessageTrackingLog -WarningAction 0 `
  -Sender "sender@example.com" -Recipients "empfaenger@example.com" `
  -Start (Get-Date).AddHours(-48) -End (Get-Date) -ResultSize Unlimited |
  Where-Object { $_.Sender -notmatch "HealthMailbox" -and $_.Recipients -notmatch "Health" } |
  Sort-Object Timestamp |
  Format-Table Timestamp, EventId, Source, Sender, @{N="Recipients";E={$_.Recipients -join ","}}, MessageSubject -AutoSize

journalctl / grep (Postfix) Built-in

journalctl -u postfix --since "48 hours ago" | grep -iE "from=<sender@example.com>|to=<empfaenger@example.com>" | tail -40
# Ohne systemd-Journal direkt im Log, dann aber ohne Zeitfilter:
grep -iE "from=<sender@example.com>|to=<empfaenger@example.com>" /var/log/mail.log | tail -40

Exchange Online

Get-MessageTraceV2 Add-on tool

Connect-ExchangeOnline
Get-MessageTraceV2 -SenderAddress "sender@example.com" -RecipientAddress "empfaenger@example.com" `
  -StartDate (Get-Date).AddHours(-48) -EndDate (Get-Date) |
  Sort-Object Received |
  Format-Table Received, SenderAddress, RecipientAddress, Subject, Status -AutoSize

Get-MessageTrace (alt) Add-on tool

# Vor der Umstellung auf V2; identische Parameter, kleinere Ergebnisgrenzen.
Get-MessageTrace -SenderAddress "sender@example.com" -RecipientAddress "empfaenger@example.com" `
  -StartDate (Get-Date).AddHours(-48) -EndDate (Get-Date)

Fenster am Vormittag

Exchange on-prem

Get-MessageTrackingLog (Exchange) Built-in

Get-TransportService | Get-MessageTrackingLog -WarningAction 0 `
  -Sender "sender@example.com" -Recipients "empfaenger@example.com" `
  -Start (Get-Date "2026-08-09 08:00") -End (Get-Date "2026-08-09 12:00") -ResultSize Unlimited |
  Where-Object { $_.Sender -notmatch "HealthMailbox" -and $_.Recipients -notmatch "Health" } |
  Sort-Object Timestamp |
  Format-Table Timestamp, EventId, Source, Sender, @{N="Recipients";E={$_.Recipients -join ","}}, MessageSubject -AutoSize

journalctl / grep (Postfix) Built-in

journalctl -u postfix --since "2026-08-09 08:00" --until "2026-08-09 12:00" | grep -iE "from=<sender@example.com>|to=<empfaenger@example.com>" | tail -40
# Ohne systemd-Journal direkt im Log, dann aber ohne Zeitfilter:
grep -iE "from=<sender@example.com>|to=<empfaenger@example.com>" /var/log/mail.log | tail -40

Exchange Online

Get-MessageTraceV2 Add-on tool

Connect-ExchangeOnline
Get-MessageTraceV2 -SenderAddress "sender@example.com" -RecipientAddress "empfaenger@example.com" `
  -StartDate (Get-Date "2026-08-09 08:00") -EndDate (Get-Date "2026-08-09 12:00") |
  Sort-Object Received |
  Format-Table Received, SenderAddress, RecipientAddress, Subject, Status -AutoSize

Get-MessageTrace (alt) Add-on tool

# Vor der Umstellung auf V2; identische Parameter, kleinere Ergebnisgrenzen.
Get-MessageTrace -SenderAddress "sender@example.com" -RecipientAddress "empfaenger@example.com" `
  -StartDate (Get-Date "2026-08-09 08:00") -EndDate (Get-Date "2026-08-09 12:00")

The two date fields set the range to the minute; left empty, the command uses the last 48 hours. Get-TransportService queries all transport servers, not just the local one. The HealthMailbox lines (Microsoft's own monitoring messages) are filtered out, otherwise real hits drown in them. Exchange Online needs the ExchangeOnlineManagement module; there Get-MessageTraceV2 has replaced Get-MessageTrace.

Search by subject

Finds messages by part of the subject: the usual route when the user can only say "my invoice from yesterday".

Exchange on-prem

Get-MessageTrackingLog (Exchange) Built-in

Get-TransportService | Get-MessageTrackingLog -WarningAction 0 `
  -MessageSubject "Rechnung" `
  -Start (Get-Date).AddHours(-48) -End (Get-Date) -ResultSize Unlimited |
  Where-Object { $_.Sender -notmatch "HealthMailbox" } |
  Sort-Object Timestamp |
  Format-Table Timestamp, EventId, Sender, @{N="Recipients";E={$_.Recipients -join ","}}, MessageSubject -AutoSize

journalctl / grep (Postfix) Built-in

journalctl -u postfix --since "48 hours ago" | grep -i "Rechnung" | tail -40
grep -i "Rechnung" /var/log/mail.log | tail -40

Exchange Online

Get-MessageTraceV2 Add-on tool

# V2 kennt keinen Betreff-Parameter, darum nachträglich filtern.
Get-MessageTraceV2 -StartDate (Get-Date).AddHours(-48) -EndDate (Get-Date) |
  Where-Object { $_.Subject -like "*Rechnung*" } |
  Format-Table Received, SenderAddress, RecipientAddress, Subject, Status -AutoSize

On-prem, -MessageSubject matches a substring. In Exchange Online the command filters afterwards, because Get-MessageTraceV2 has no subject parameter.

Trace one message

Shows every station of a specific message by its message ID, across all transport servers.

Message-ID aus dem Kopf

Exchange on-prem

Get-MessageTrackingLog (Exchange) Built-in

Get-TransportService | Get-MessageTrackingLog -WarningAction 0 `
  -MessageId "<a1b2c3@example.com>" `
  -Start (Get-Date).AddHours(-48) -End (Get-Date) -ResultSize Unlimited |
  Sort-Object Timestamp |
  Format-Table Timestamp, ServerHostname, EventId, Source, SourceContext, @{N="Recipients";E={$_.Recipients -join ","}} -AutoSize

grep (Postfix) Built-in

qid=$(grep -F "<a1b2c3@example.com>" /var/log/mail.log | grep -oE "[A-F0-9]{8,}" | head -1)
echo "Queue-ID: $qid"
grep "$qid" /var/log/mail.log

Exchange Online

Get-MessageTraceV2 Add-on tool

Get-MessageTraceV2 -MessageId "<a1b2c3@example.com>" `
  -StartDate (Get-Date).AddHours(-48) -EndDate (Get-Date) |
  Format-List Received, SenderAddress, RecipientAddress, Subject, Status, MessageTraceId

The message ID is in the message header (our mail header analyzer shows it). Include the angle brackets exactly as in the header.

Delivery path with time offset

The delivery path as a chain with seconds since the first event: shows which station lost the time.

Exchange on-prem

Get-MessageTrackingLog (Exchange) Built-in

$log = Get-TransportService | Get-MessageTrackingLog -WarningAction 0 `
  -MessageId "<a1b2c3@example.com>" -Start (Get-Date).AddHours(-48) -End (Get-Date) -ResultSize Unlimited |
  Sort-Object Timestamp
$first = $log[0].Timestamp
$log | Select-Object @{N="+Sek";E={[math]::Round(($_.Timestamp - $first).TotalSeconds,1)}},
  Timestamp, ServerHostname, EventId, Source, @{N="Detail";E={$_.RecipientStatus -join " "}} |
  Format-Table -AutoSize
# Kette: RECEIVE -> SUBMIT -> TRANSFER -> SEND (nach aussen) bzw. DELIVER (ins Postfach).

grep / awk (Postfix) Built-in

qid=$(grep -F "<a1b2c3@example.com>" /var/log/mail.log | grep -oE "[A-F0-9]{8,}" | head -1)
grep "$qid" /var/log/mail.log | awk '{ print $1, $2, $3, $6, $7, $8, $9, $10 }'

Exchange Online

Get-MessageTraceDetailV2 Add-on tool

$m = Get-MessageTraceV2 -MessageId "<a1b2c3@example.com>" -StartDate (Get-Date).AddHours(-48) -EndDate (Get-Date)
$m | ForEach-Object {
  Get-MessageTraceDetailV2 -MessageTraceId $_.MessageTraceId -RecipientAddress $_.RecipientAddress |
    Format-Table Date, Event, Action, Detail -AutoSize
}

The EventId chain reads RECEIVE, SUBMIT, TRANSFER and finally SEND (outbound) or DELIVER (into the mailbox). FAIL or DEFER mark the breaking point.

Failed deliveries

Groups the FAIL events by reason, most frequent first: finds the pattern behind an outage instead of single cases.

Letzte 48 Stunden

Exchange on-prem

Get-MessageTrackingLog (Exchange) Built-in

Get-TransportService | Get-MessageTrackingLog -WarningAction 0 -EventId FAIL `
  -Start (Get-Date).AddHours(-48) -End (Get-Date) -ResultSize Unlimited |
  Where-Object { $_.Sender -notmatch "HealthMailbox" } |
  Group-Object { $_.RecipientStatus -join " " } -NoElement |
  Sort-Object Count -Descending | Select-Object -First 15 Count, Name

grep (Postfix) Built-in

grep "status=bounced" /var/log/mail.log |
  grep -oE "said: [0-9]{3}[ -][0-9.]* [^)]*" | sort | uniq -c | sort -rn | head -15

Exchange Online

Get-MessageTraceV2 Add-on tool

Get-MessageTraceV2 -Status Failed -StartDate (Get-Date).AddHours(-48) -EndDate (Get-Date) |
  Group-Object RecipientAddress -NoElement | Sort-Object Count -Descending | Select-Object -First 15

Find slow deliveries

Computes the time from receipt to delivery per message and shows the slowest: the defensible answer to "mail is so slow".

Exchange on-prem

Get-MessageTrackingLog (Exchange) Built-in

$log = Get-TransportService | Get-MessageTrackingLog -WarningAction 0 `
  -Start (Get-Date).AddHours(-48) -End (Get-Date) -ResultSize Unlimited |
  Where-Object { $_.Sender -notmatch "HealthMailbox" }
$log | Group-Object MessageId | ForEach-Object {
  $recv = ($_.Group | Where-Object EventId -eq "RECEIVE" | Sort-Object Timestamp | Select-Object -First 1).Timestamp
  $done = ($_.Group | Where-Object { $_.EventId -in "DELIVER", "SEND" } | Sort-Object Timestamp | Select-Object -Last 1).Timestamp
  if ($recv -and $done) {
    [pscustomobject]@{ Sekunden = [math]::Round(($done - $recv).TotalSeconds, 1); Betreff = $_.Group[0].MessageSubject }
  }
} | Sort-Object Sekunden -Descending | Select-Object -First 15

grep (Postfix) Built-in

grep -oE "delay=[0-9.]+" /var/log/mail.log | cut -d= -f2 | sort -rn | head -15
# delay = Gesamtzeit von der Annahme bis zur Zustellung in Sekunden.

Exchange Online

Get-MessageTraceV2 Add-on tool

# EXO liefert keine Laufzeit je Nachricht; die Detailansicht zeigt die Stationen mit Zeitstempel.
Get-MessageTraceV2 -StartDate (Get-Date).AddHours(-48) -EndDate (Get-Date) |
  Sort-Object Received -Descending | Select-Object -First 25 Received, SenderAddress, RecipientAddress, Subject, Status

Top senders and recipients

Counts volume per sender and recipient: useful for load questions and when a mailbox is suspected of being compromised.

Exchange on-prem

Get-MessageTrackingLog (Exchange) Built-in

$log = Get-TransportService | Get-MessageTrackingLog -WarningAction 0 -EventId RECEIVE `
  -Start (Get-Date).AddHours(-48) -End (Get-Date) -ResultSize Unlimited |
  Where-Object { $_.Sender -notmatch "HealthMailbox" }
"--- Top-Absender ---"
$log | Group-Object Sender -NoElement | Sort-Object Count -Descending | Select-Object -First 10
"--- Top-Empfaenger ---"
$log | ForEach-Object { $_.Recipients } | Group-Object -NoElement | Sort-Object Count -Descending | Select-Object -First 10

grep (Postfix) Built-in

grep -oE "from=<[^>]+>" /var/log/mail.log | sort | uniq -c | sort -rn | head -10
grep -oE "to=<[^>]+>" /var/log/mail.log | sort | uniq -c | sort -rn | head -10

Exchange Online

Get-MessageTraceV2 Add-on tool

$t = Get-MessageTraceV2 -StartDate (Get-Date).AddHours(-48) -EndDate (Get-Date)
"--- Top-Absender ---"
$t | Group-Object SenderAddress -NoElement | Sort-Object Count -Descending | Select-Object -First 10
"--- Top-Empfaenger ---"
$t | Group-Object RecipientAddress -NoElement | Sort-Object Count -Descending | Select-Object -First 10

Filter by EventId

Shows all events of one type, for example only FAIL, DEFER or DELIVER.

Exchange on-prem

Get-MessageTrackingLog (Exchange) Built-in

Get-TransportService | Get-MessageTrackingLog -WarningAction 0 -EventId FAIL `
  -Start (Get-Date).AddHours(-48) -End (Get-Date) -ResultSize Unlimited |
  Sort-Object Timestamp |
  Format-Table Timestamp, ServerHostname, Sender, @{N="Recipients";E={$_.Recipients -join ","}}, MessageSubject -AutoSize
# Gaengige EventIds: RECEIVE SEND DELIVER FAIL DEFER RESOLVE EXPAND REDIRECT DROP SUBMIT TRANSFER

grep (Postfix) Built-in

grep -oE "status=[a-z]+" /var/log/mail.log | sort | uniq -c | sort -rn

Exchange Online

Get-MessageTraceV2 Add-on tool

# EXO kennt keine EventIds, sondern einen Status:
# Delivered, Failed, Pending, Quarantined, FilteredAsSpam, Expanded
Get-MessageTraceV2 -Status Failed -StartDate (Get-Date).AddHours(-48) -EndDate (Get-Date) |
  Format-Table Received, SenderAddress, RecipientAddress, Subject, Status -AutoSize

Exchange Online has no EventIds but a status (Delivered, Failed, Pending, Quarantined, FilteredAsSpam).

Server overview

Counts one server's events by type and shows whether the tracking log is enabled at all and how long it is kept.

Exchange on-prem

Get-MessageTrackingLog (Exchange) Built-in

Get-MessageTrackingLog -Server mbx01.example.com -WarningAction 0 `
  -Start (Get-Date).AddHours(-48) -End (Get-Date) -ResultSize Unlimited |
  Where-Object { $_.Sender -notmatch "HealthMailbox" } |
  Group-Object EventId -NoElement | Sort-Object Count -Descending

Get-TransportService Built-in

Get-TransportService | Select-Object Name, MessageTrackingLogEnabled, MessageTrackingLogPath, MessageTrackingLogMaxAge

Exchange Online

Get-ConnectionByClientTypeReport Add-on tool

# EXO hat keine einzelnen Server; die Auswertung laeuft ueber Mandant-Berichte.
Get-MailTrafficATPReport -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date) | Format-Table Date, EventType, MessageCount

By default Exchange keeps tracking logs for 30 days; older incidents can no longer be evidenced.

Export for tickets

Writes the result as CSV with semicolons and UTF-8: opens straight in Excel and attaches to a ticket.

Exchange on-prem

Export-Csv (Exchange) Built-in

Get-TransportService | Get-MessageTrackingLog -WarningAction 0 -Sender "sender@example.com" `
  -Start (Get-Date).AddHours(-48) -End (Get-Date) -ResultSize Unlimited |
  Where-Object { $_.Sender -notmatch "HealthMailbox" } |
  Select-Object Timestamp, ServerHostname, EventId, Source, Sender,
    @{N="Recipients";E={$_.Recipients -join ";"}}, MessageSubject,
    @{N="Status";E={$_.RecipientStatus -join " "}} |
  Sort-Object Timestamp |
  Export-Csv .\tracking.csv -NoTypeInformation -Encoding UTF8 -Delimiter ";"

grep / awk (Postfix) Built-in

grep -iE "from=<sender@example.com>" /var/log/mail.log |
  awk '{ print $1" "$2" "$3";"$4";"$6" "$7" "$8 }' > tracking.csv
head -5 tracking.csv

Exchange Online

Export-Csv (Exchange Online) Add-on tool

Get-MessageTraceV2 -SenderAddress "sender@example.com" `
  -StartDate (Get-Date).AddHours(-48) -EndDate (Get-Date) |
  Export-Csv .\trace.csv -NoTypeInformation -Encoding UTF8 -Delimiter ";"

The output contains sender, recipient and subject. Check whether that is acceptable for the audience before sharing it.

Other areas