Skip to content

About

Report the Exchange Online messages sent to more than 25 recipients from the message trace (Microsoft Graph to SQLite to CSV/HTML), counted before distribution list expansion, one row per Message ID. PowerShell 7.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

19 Commits

Folders and files

Repository files navigation

Recipient Limit Report: the Exchange Online messages sent to more than 25 recipients, counted as Exchange Online counts them (a distribution list is one recipient), from the message trace, as CSV and HTML reports with one row per Message ID; the report of Purview DLP Report without any DLP policy

Why  ·  How it works  ·  Same report as Purview DLP Report  ·  Reports  ·  Quick start  ·  User guide  ·  Developer guide

Important

Files downloaded from the Internet may be blocked by Windows and fail to run. Before using this project, unblock every file in the downloaded folder:

Get-ChildItem "C:\Chemin\Du\Dossier" -Recurse -File -Force | Unblock-File

Replace the example path with the folder where you downloaded or extracted this project.

The Install-Module commands in this documentation use -Force, so they also update or reinstall a module that is already installed. If an older version still conflicts, close every PowerShell window, open a new one (as administrator for -Scope AllUsers), run Uninstall-Module <ModuleName> -AllVersions -Force, then run the Install-Module command again.

Why

To reduce the volume of e-mail, organisations often want to limit the messages sent to a large audience — for example to more than 25 recipients, except for the users of an exception list. The limit is applied per mailbox (Set-Mailbox -RecipientLimits) or by a DLP policy. In both cases the business lines need to see which messages are concerned — who sends, to whom, about what, when — and the administrators need facts to maintain the exception list.

Purview DLP Report answers it from a DLP policy in audit mode. This tool produces the same report from the message trace, without any DLP policy — and keeps seeing the messages once RecipientLimits refuses them, before any DLP rule runs.

What the report answers: who sends to more than 25 recipients; the source is the Exchange Online message trace, 90 days; a local SQLite history; CSV and HTML files. Why the message trace: independent of DLP, works with RecipientLimits, same numbers as the DLP report

How it works

The message trace is collected every day into a local SQLite database, the messages sent to distribution lists are counted before expansion, then the CSV and HTML report is written; three modes: Report, Collect, Status
  • The whole message trace is read (Microsoft Graph messageTraces): the API cannot filter on a number of recipients. Target.SenderDomains limits the reading to the senders of the organisation. One shared rolling budget of 90 requests per 5 minutes (configurable), Retry-After honoured, token renewed — the quota of the tenant, not the server, sets the pace: about 90,000 rows per minute.
  • Page by page into SQLite: Ctrl+C or a failure keeps what was received; the next run collects only the rest. The last hours, which may still change, are read again. Once a period has settled, only the messages over the limit are kept.
  • A distribution list is one recipient, as for RecipientLimits and the DLP condition. For a message sent to a list, the count before expansion is read from the Submit event of its route, and the recipient list as the sender addressed it is rebuilt from the route of each recipient: the members added by the expansion are left out.
  • The report of Purview DLP Report: one row per Message ID — received time, sender, recipients (optional), subject, recipient count, distribution lists, Message ID — in CSV and a self-contained HTML file. Large periods are split by day, week or number of rows.
  • Read-only, one permission (ExchangeMessageTrace.Read.All), certificate authentication built in — no module to install, SQLite bundled.

Same report as Purview DLP Report

Both tools on the same 48-hour window of a lab tenant, from empty databases:

48-hour window: the 149,802 messages of the DLP report are all found with the same recipient count; 150,154 messages over 25 recipients, 352 more than the DLP rule recorded; for the 10 pilot messages sent to distribution lists, the same recipient list as the DLP report

The 352 messages more are messages over 25 recipients that the DLP rule never evaluated, or blocked without any event in Activity Explorer: the message trace records every message. On 7 days of a load test (13.2 million trace rows), a run from an empty database took 3 h 03 min for 400,139 messages over 25 recipients; the daily collection then reads one day at a time.

Reports

HTML report overview
HTML report · tiles, recipient count bands, Top 10 senders, filters and a virtual list of every message
A message sent to distribution lists
A message · 28 recipients before expansion, the 2 distribution lists first, the members added by the expansion left out
A report in the console
Console · plan, Microsoft Graph, collection, count before expansion, files
The database day by day
Status · what the database holds, day by day, and what can still be collected

Each run writes a CSV file (one row per Message ID) and a self-contained HTML report that stays fast with 400,000 messages. Large periods are split by week, day or number of rows.

Requirements

Item Requirement
PowerShell 7.4 or later, Windows
Modules None (certificate or secret). Interactive sign-in: Microsoft.Graph.Authentication
Permission Microsoft Graph ExchangeMessageTrace.Read.All with admin consent
Tenant The service principal of the Microsoft application 8bd644d1-64a1-4d4b-ae52-2e0cbf64e373 (guide, chapter 7)
Console Windows Terminal (emoji and colours)
SQLite Bundled in lib\sqlite — nothing to install

Quick start

git clone https://github.com/Nico77600/RecipientLimitReport.git
cd RecipientLimitReport\package
notepad .\config\RecipientLimitReport.config.psd1      # TenantId, AppId, CertificateThumbprint, sender domains

.\Invoke-RecipientLimitReport.ps1 -Mode Status          # checks the configuration, no connection
.\Invoke-RecipientLimitReport.ps1                       # report of the last 7 days
.\Invoke-RecipientLimitReport.ps1 -Range PreviousMonth  # previous calendar month
.\Invoke-RecipientLimitReport.ps1 -Range Day -Date 2026-09-28 -IncludeRecipientDetails:$false
.\Invoke-RecipientLimitReport.ps1 -Mode Collect         # daily collection (scheduled task)

The message trace keeps 90 days: schedule the daily collection. The package folder of this repository holds exactly the files needed to run Recipient Limit Report, with both guides. The zip of each release contains the same run-time files with the HTML guides; .\tools\New-RlrPackage.ps1 builds that zip content from the repository.

Documentation

Guide Content
User guide What is needed before the first report, the one-time setup in the package folder or the extracted release, then one command per everyday question: a period, the daily collection, the state of the database. Where the files are written, how to read the report, the exit codes, and the situations that come back with what to do.
Developer guide Everything else: the project background, how the tool works, the prerequisites and the application registration with a certificate, every configuration key, the scheduled collection, the count before distribution list expansion, how to read the report, output files and splitting, volume and duration, the internals and the code map, how to modify and test the tool, troubleshooting, the lab measurements and the database schema.

Both guides also exist as a single HTML file with a light and a dark theme (package/docs/RecipientLimitReport-UserGuide.html, package/docs/RecipientLimitReport-Guide.html): download them and open them locally, or use the copies in the release zip.

Tests

Invoke-Pester -Path .\tests      # Pester 5+, in-memory message trace API, no connection to Microsoft 365

tools\Build-Documentation.ps1 rebuilds the HTML guides; tools\New-ReadmeImages.ps1 renders the graphics of this page from the guide, in a light and a dark version.

License

MIT. The bundled SQLite components keep their own licenses: see THIRD-PARTY-NOTICES.md.

Disclaimer

This Script is a Personal project. It's provided "AS-IS". It's not an official Microsoft product so no support can be expected from Microsoft.

As any scripts you must read carefully the documentation and test it first in a Test environment before any run in Production.

About

Report the Exchange Online messages sent to more than 25 recipients from the message trace (Microsoft Graph to SQLite to CSV/HTML), counted before distribution list expansion, one row per Message ID. PowerShell 7.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages