Use Message Tracing to Report Exchange Online Email Sent to External Recipients

Monitor Who’s Sending Email Outside the Organization

A question about message tracing arose last week in the Facebook Office 365 Technical Discussions group:

I have a request from management to report for a set of users where I need to report how many emails they send outside of the company, the recipients and date and time.”

My initial response was that the management involved must be devotees of mid-20th century time and motion methodologies. Further reflection brought me to the point where the request might be justifiable by some form of investigation into suspected wrongdoing. Hopefully, management has legal and HR advice about reporting user activity in this way. Assuming they do, it’s an interesting technical problem to solve.

Message Tracing Provides the Data

Exchange Online message tracing is core to the solution. You can track the path of messages through the Exchange Online transport system using GUIs in the Exchange admin center or Microsoft 365 Defender portal. Message tracing data is also available through PowerShell using the Get-MessageTrace cmdlet, which is what we use here.

Exchange Online retains message trace data for up to 90 days. However, only the last ten days are available online. To track messages sent to external recipients, we can interrogate the message tracing data and report what we find. This isn’t the only useful purpose for message tracing. For instance, you can understand how active distribution lists are by tracking their email activity.

Identify Target Mailboxes

First, we need to identify the set of target mailboxes to monitor. The simplest method is to run the Get-ExoMailbox cmdlet to find a set of mailboxes belonging to a certain department or matching another attribute. For example, this command returns all the mailboxes with the Office property set to Boston:

[array]$Mbx = Get-EXOMailbox -RecipientTypeDetails UserMailbox -ResultSize Unlimited -Filter {Office -eq 'Boston'} -Properties Office

Given the nature of investigations, you might not find a suitable property to query against. In this situation, using one of the 15 custom attributes available for mailboxes is a good way to mark the target mailboxes.

I decided to use a different approach by creating a distribution group containing the target mailboxes. The account assigned ownership of the group will receive the reports. To avoid users seeing the new group, I hid it from address lists and gave it a difficult to guess email address:

New-DistributionGroup -Name "Monitored Users" -Alias Monitored.Users -DisplayName "Users External Email Monotoring" -IgnoreNamingPolicy
Set-DistributionGroup -Identity Monitored.Users -HiddenFromAddressListsEnabled $True -ManagedBy Compliance.Administrator -PrimarySmtpAddress Monitored.Users.Investigation101@office365itpros.com
Add-DistributionGroupMember -Identity Monitored.Users -Member Kim.Akers
Add-DistributionGroupMember -Identity Monitored.Users -Member Terry.Hegarty
Add-DistributionGroupMember -Identity Monitored.Users -Member Chris.Bishop
Add-DistributionGroupMember -Identity Monitored.Users -Member James.Ryan

Message Tracing in PowerShell

With a set of target mailboxes, we can go ahead and do some message tracing to find details of their external email. Here’s the loop I used to run a message trace for each mailbox and extract records for external email by checking the recipient address. A PowerShell list stores details of the messages for use later.

Write-Host ("Checking external email for {0} mailboxes" -f $Users.count)
$Report = [System.Collections.Generic.List[Object]]::new() 
ForEach ($User in $Users) {
   Write-Host ("Checking messages sent by {0}" -f $User.DisplayName)
   # Get message information for the last ten days and filter so that we end up with just external addresses
   [string]$SenderAddress = $User.PrimarySmtpAddress
   [array]$Messages = Get-MessageTrace -StartDate $StartDate -EndDate $EndDate -SenderAddress $SenderAddress -Status Delivered | ? {$_.RecipientAddress -notlike "*@Office365itpros*"}
   ForEach ($M in $Messages) {
     $ReportLine = [PSCustomObject][Ordered]@{
          Date      = Get-Date($M.Received) -format g 
          User      = $M.SenderAddress
          Recipient = $M.RecipientAddress
          Subject   = $M.Subject
          MessageId = $M.MessageId }
     $Report.Add($ReportLine)
   } #End Foreach messages
} # End ForEach Users

Notifying Management by Email

The request didn’t specifically say how management should receive the report of external email activity. It’s easy to create output from the data and provide it as a CSV file or HTML document. For the purposes of this exercise (and because we have some suitable code), we’ll email the report using cmdlet from the Microsoft Graph PowerShell SDK.

Management say that they want to know about external recipients and message timestamps. I assume they also want to know who sends messages and the message subjects, so we’ll take that information extracted from the message traces and format it as a HTML bodypart.

The next steps are to select the mailbox to send the message, its recipient, and a subject. I use the logged-in user to send the message to the person who manages the distribution list. Alternatively, you could hardcode these assignments. Here’s what I did:

$MsgFrom = $O365Cred.UserName
[string]$EmailRecipient = (Get-DistributionGroup -Identity Monitored.Users).ManagedBy
[string]$EmailRecipientAddress = (Get-EXOMailbox -Identity $EmailRecipient).PrimarySmtpAddress
$MsgSubject = "User External Email Report"

Finally, we build the message body using the HTML bodypart, create a draft message with the New-MgUserMessage cmdlet, and send it with Send-MgUserMessage. Here’s the relevant code:

# Construct the message body
     $MessageBody = @{
         content = "$($Body)"
         ContentType = 'html'
     }
# Create a draft message in the signed-in user's mailbox
$NewMessage = New-MgUserMessage -UserId $MsgFrom -Body $MessageBody -ToRecipients $EmailToRecipient -Subject $MsgSubject
# Send the message
Send-MgUserMessage -UserId $MsgFrom -MessageId $NewMessage.Id

Figure 1 shows what the message received by the manager looks like. The HTML formatting is basic and could be made much prettier if that need exists.

The message tracing report for external email activity
Figure 1: The message tracing report for external email activity

Making Things Work

Although I don’t agree with the idea of monitoring user email activity in any way, this is an interesting example of using the message tracing information available to all Exchange Online tenants to deliver a reasonable response to a request. Combined with some PowerShell snippets from other scripts, it’s possible to create a fully working solution in a few hours. To get started, you can download my code from GitHub.


Learn how to exploit the data available to Microsoft 365 tenant administrators through the Office 365 for IT Pros eBook. We love figuring out how things work.

2 Replies to “Use Message Tracing to Report Exchange Online Email Sent to External Recipients”

    1. Yes, you can search using eDiscovery. However, I suggest that there’s more work to do using that approach. The ask was to generate a report. You could do the same by running a compliance search and interpreting the results, but you wouldn’t get details like the email subject, which means that someone would then have to open the search results (exported to a PST) and check each message manually. That’s certainly a way to detect malfeasance, but it’s not the approach I would take.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.