Message tracking
Search the tracking log and prove the delivery path: Exchange on-prem, Exchange Online and Postfix.
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
- From (date and time)
-
2026-08-09T08:00Change in the builder → - To (date and time)
-
2026-08-09T12:00Change in the builder →
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".
- Subject (part)
-
RechnungChange in the builder →
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
- Message ID
-
<a1b2c3@example.com>Change in the builder →
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.
- Message ID
-
<a1b2c3@example.com>Change in the builder →
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.
- EventId
-
FAILChange in the builder →
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 server
-
mbx01.example.comChange in the builder →
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.