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-FileReplace 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.
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.
- The whole message trace is read (Microsoft Graph
messageTraces): the API cannot filter on a number of recipients.Target.SenderDomainslimits the reading to the senders of the organisation. One shared rolling budget of 90 requests per 5 minutes (configurable),Retry-Afterhonoured, 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
RecipientLimitsand 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.
Both tools on the same 48-hour window of a lab tenant, from empty databases:
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.
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.
| 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 |
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.
| 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.
Invoke-Pester -Path .\tests # Pester 5+, in-memory message trace API, no connection to Microsoft 365tools\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.
MIT. The bundled SQLite components keep their own licenses: see THIRD-PARTY-NOTICES.md.
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.



