Blocking Junk Email from Spammy Domains Remains a Problem for Exchange Online

Recently, I participated in a Directions on Microsoft podcast featuring Mary-Jo Foley and Rob Helm. The subject of the podcast was “All about Exchange,” which is a pretty big space to cover in 30 minutes, especially in view of the retirement of Exchange 2016 and 2019, the move to embrace subscription-based licensing, and the changes implemented over the last few years in Exchange Online to protect the integrity of the service, like limiting the use of MOERA domains to send external email.

During our discussion, Mary Jo complained about the amount of spam that arrived in her inbox and asked if anything could be done to suppress the unwanted email. My response was that squashing spam needs constant administrator attention, no matter what anti-spam technology is deployed. Microsoft Defender for Office 365 is a good solution, but spammers have proven over the years that they are highly innovative when it comes to getting unsolicited commercial email past defences, and some of their output will always get through.

Using PowerShell to Analyze Junk Email

All of which got me to think whether it is possible to improve matters by using PowerShell to apply some intelligence to the fight by creating a transport rule to block email based on the messages that end up in the Junk Email folder in user mailboxes.

The plan was simple. If anti-spam processing delivers a message to a user’s Junk Email folder, it creates a signal that can be used to suppress future messages from the same source. By scanning the Junk Email folders of all user and shared mailboxes in a tenant, we can build a list of domains that send email that we might want to suppress.

Not every message that ends up in the Junk Email folder is spam, but a high percentage are. I think it’s reasonable to use this data to guide automatic suppression by creating or updating a transport rule to block messages from the domains as an SMTP connector delivers the messages to Exchange Online.

Writing the Script to Analyze Junk Email and Block Spammy Domains

Writing the code wasn’t particularly challenging. Because access is required to every mailbox in the tenant (unless limited by RBAC for Applications), we need to use application permissions, and that means running the Microsoft Graph PowerShell SDK in app-only mode (where a registered app holds the permissions and credentials) or using an Azure Automation runbook and a managed identity. Either approach will work nicely. The Graph permissions required are MailboxFolder.Read.All, Mail.ReadBasic.All, and Mail.Send to access folders, read messages, and send email with the results.

The steps in the code are:

  • Connect to the Microsoft Graph and to Exchange Online. Use this order to avoid the assembly mismatch problem that currently affects Microsoft 365 modules.
  • Find the set of user mailboxes and shared mailboxes to check.
  • For each mailbox, find the Junk email folder using the Get-MgUserMailFolder cmdlet. “JunkEmail” is one of Outlook’s “well-known folders,” so this value can be used to find the folder no matter what language a mailbox uses.
  • Run the Get-MgUserMailFolderMessage cmdlet to check the Junk Email folder for messages and extract the domain that sent the spam.
  • After processing all mailboxes, generate a list of domains that have sent us spam. Extracting domains from email addresses for all public suffixes took a surprising amount of effort, and GitHub Copilot eventually produced a function that works well. Making sure that top-level domains (in other words, contoso.com rather than mail.contoso.com) are used to block messages with transport rules is important because the SenderDomainIs condition has a 4096 character limit.
  • Exclude some consumer domains like gmail.com and outlook.com from the list of domains to block.
  • Send the list of domains and a recommendation to create or update a transport rule to administrators. The message contains the PowerShell code to create the transport rule (Figure 1).
Email to administrators to advise them how to create a transport rule to block spammy domains.
Figure 1: Email to administrators to advise them how to create a transport rule to block spammy domains

Deleting Junk Email

By default, the script doesn’t remove any of the items from the Junk Email folder. Exchange Online cleans items out automatically 15 days after they arrive, so there’s no need to do anything. However, some folks might like to clean up as they go, so I added some code to remove the junk mail items if the $DeleteItemsNow variable is true (either set explicitly or via a parameter). The code does this:

        If ($DeleteItemsNow) {
            Write-Host ("Deleting {0} items from the Junk Email folder for {1}" -f $MailItems.Count, $M.DisplayName) -ForegroundColor Yellow
            ForEach ($MailItem in $MailItems) {
                Try {
                    Remove-MgUserMailFolderMessage -UserId $M.ExternalDirectoryObjectId -MailFolderId $JunkEmailFolder.Id -MessageId $MailItem.Id -ErrorAction Stop
                } Catch {
                    Write-Host ("Failed to delete item {0} from the Junk Email folder for {1}" -f $MailItem.Id, $M.DisplayName) -ForegroundColor Red
                }
            }
    }

This code moves the junk mail items into the Deleted Items folder. If you want, you can remove items permanently by running the Remove-MgUserMessagePermanent cmdlet instead. Using this cmdlet requires the app to have the Mail.ReadWrite permission, which is obviously a very powerful permission. Consider using RBAC for Applications to limit access to sensitive mailboxes, or, if you allow the app to process sensitive mailboxes, consider whether the app should be allowed to remove items from these mailboxes.

An Active Transport Rule

There’s no point in me writing about something to make a recommendation if I don’t follow the advice myself. The spammy domains transport rule is now active in my tenant (Figure 2). and is doing a good job of moving inbound messages from the junk email domains to the quarantine. The advantage of using the quarantine is that messages can be retrieved by users or administrators if a blocked domain turns out to be a source of good email.

Details of the transport rule to block spammy domains
Figure 2: Details of the transport rule to block spammy domains

Like any automated suggestions (including those generated by AI), it’s always a good idea to review the proposed actions. For instance, the first version of the script included msn.com in the list of spammy domains. Msn.com is now excluded, but you can add it back if you like.

Increase in Notification Messages

The only disadvantage I’ve noticed is that users might receive more quarantine notification messages than they’re used to. The number of quarantine notifications can be moderated by increasing the notification interval from daily to weekly, and you can also add some custom text to the notifications to inform recipients about the new method of redirecting junk email to quarantine.

Long Term Maintenance for the Spammy Domains Rule

Over time, new spam will arrive in user mailboxes. If you run the script again, it will find and process the new spam and generate an updated list of junk email domains. To update the transport rule, here’s some code to fetch the set of currently defined domains, combine the set with the domains found by the script, and update the transport rule with the combined set:

[array]$ExistingDomains = (Get-TransportRule -Identity 'Quarantine Traffic from Junk Email Domains').SenderDomainIs
$JunkEmailDomains += $ExistingDomains
# Remove duplicates
$JunkEmailDomains = $JunkEmailDomains | Sort-Object -Unique
Set-TransportRule -Identity 'Quarantine Traffic from Junk Email Domains' -SenderDomainIs $JunkEmailDomains

Embrace the Graph and Get to Know Microsoft 365 Better

You can download the complete script from the Office 365 for IT Pros GitHub repository. Remember, this code is intended to illustrate principles rather than being a fully finished solution. You’ll need to adjust the code to adjust the values for the registered app, tenant identifier, and certificate (or app secret) for your tenant before the code can run.

Tenant administrators should embrace PowerShell and get to know how to use the Graph so that they achieve a better understanding of how Microsoft 365 works. This is just a small example of how knowledge of PowerShell and the Graph can improve how a tenant works. It’s a good enough reason to embrace the Graph.

I don’t know if Mary Jo Foley will use the script to help suppress spam. That might be too much of a step for her. But I can dream…

About the Author

Tony Redmond

Tony Redmond has written thousands of articles about Microsoft technology since 1996. He is the lead author for the Office 365 for IT Pros eBook, the only book covering Office 365 that is updated monthly to keep pace with change in the cloud. Apart from contributing to Practical365.com, Tony also writes at Office365itpros.com to support the development of the eBook. He has been a Microsoft MVP since 2004.

Leave a Reply