MSP monthly report contents

12 Aug 2026

What an MSP monthly report should actually contain

Here is a test worth running on your own deliverable. Open last month's report for your biggest client, and count how many of its pages would change anything if they were deleted. In most decks the honest answer is one or two — and they are usually not the first two. The rest is proof of work: charts that show the same shape they showed in October, generated because the export was already open.

This is the skeleton that survives that test. Five sections, in the order a client reads them, each with a done-condition you can check before you send it.

The rule

A monthly report is a diff, not a snapshot. If a number is the same as last month, it belongs in the appendix. If it moved and nobody can act on it, it belongs nowhere. Everything that survives both filters goes on page one.

Which numbers earn a page

Run every candidate metric through three questions before it gets a chart. Most will not survive, and that is the point — the report gets shorter as the service gets better, which is a message worth sending on its own.

DOES THIS NUMBER BELONG IN THE REPORT? A number you could report Did it move since last month? against your own stored baseline no Appendix steady state is evidence, not narrative yes Can anyone act on it now? a number with no owner is trivia no Appendix true, but not this month's work yes Euro or risk attached? the two things clients act on no Cut it it was a chart, not a finding yes Front page — one line one number, one owner, one date

The five sections, in this order

Money first. Not because it matters most, but because it is the only section the person who signs the invoice will definitely read, and the goodwill it buys is what gets the security section read too.

1
Seats bought versus seats assigned.

One table, one row per SKU, two columns. Graph gives you both directly: subscribedSku exposes prepaidUnits (what you are billed for) next to consumedUnits, which is defined as the number of licences that have been assigned — not used, assigned. The gap between the two is money you are paying for nobody. Watch capabilityStatus too: a SKU sitting in Warning is a subscription in its grace period, which is a renewal conversation you would rather have this month than after it lapses.

Done when: every non-zero gap has a name next to it

2
Assigned but unused — the reclaim list.

Named people, with a last-sign-in date and a recommendation. This is the section that pays for the service, and it is also the one with the sharpest trap: the signInActivity property requires an explicit $select, needs Entra ID P1 or P2 plus AuditLog.Read.All, and — the part that ruins reports — is simply not returned for a user who never signed in, or who last signed in before April 2020. An empty field is not a zero. Sort naively and your most dormant accounts fall off the bottom of the list.

Done when: blanks are counted as never, not skipped

3
Security posture, as three numbers and one exception list.

MFA registration coverage, privileged account count, and whatever your baseline policy set is missing. Then the exceptions — by name, because "94% coverage" is a number nobody can act on and "these six admins" is a ticket. Keep the same three numbers every month even when they do not move; this is the one place where the flat line is the message, and where the appendix rule is worth breaking on purpose.

Done when: each gap names a person, not a percentage

4
Azure spend against a number somebody agreed to.

A spend chart on its own is decoration. A spend chart against a budget is a conversation. Cost Management budgets are evaluated every 24 hours against cost data that is itself 8–24 hours behind, and they take up to five thresholds each. Say the honest part out loud in the report: a budget notifies, it does not stop consumption — "resources aren't affected, and your consumption isn't stopped". The client should know the guardrail is a smoke alarm, not a sprinkler.

Done when: every subscription has a budget and a named recipient

5
What broke, and whose fault it was.

Incidents that touched this tenant, separated into yours and Microsoft's. The serviceAnnouncement resource carries all three feeds you need: healthOverviews for current service state, issues for the incidents themselves, and messages for the Message Center posts announcing changes heading your way. Two Microsoft incidents listed with dates and durations will do more for a renewal than any chart in the deck.

Done when: the client stops asking "was that outage us?"

Every section has a ceiling — put it in the footnote

The credibility of a monthly report comes from admitting what it cannot see. Each of the five sections is drawing on a surface with a hard limit, and clients find out about those limits at the worst possible moment: when they ask for something last quarter.

Section Source The ceiling to disclose
Seats subscribedSku Assigned, never used — the count says nothing about activity
Reclaim list signInActivity P1/P2 only; absent for never-signed-in and pre-April-2020 accounts
Adoption M365 usage reports Names concealed by default; only 7/30/90/180-day windows exist
Security Entra sign-in and audit logs 7 days on Free, 30 on P1/P2 — and upgrades are not retroactive
Spend Cost Management Costs land 8–24h late; budgets alert but never stop consumption

Two of those deserve a sentence in the report itself. Usage reports conceal usernames, groups and site names by default, and that organisation setting follows you into Graph and Power BI — so the adoption section is either anonymous or the client has explicitly turned concealment off, and they should know which. And when a user account is deleted, their usage data goes within 30 days: the person whose licence you reclaimed in section 2 takes the evidence for it with them. Reclaim first, screenshot later, is the wrong order.

The section that makes it a report

Everything above is available to anyone with the right admin roles. What makes it a monthly report rather than a monthly export is the column that Microsoft will not give you: last month's value. Entra keeps sign-in and audit logs for seven days on Free and 30 days on P1 or P2, and explicitly warns that retention changes are not retroactive — buying P1 today does not resurrect last month. Usage data reaches 180 days and stops.

Which means a quarterly comparison is not something you query. It is something you kept, or it does not exist. Store each month's five sections as you produce them, and the diff column writes itself; skip a month because month-end was busy and that month is permanently blank, which is the real cost of assembling reports by hand — not the hours, the gaps they leave behind.

What to leave out

Storage-by-user charts with no quota pressure. Compliance percentages with no named exception. Anything with the word "engagement" in the title. Screenshots of admin center blades, which age badly and prove only that you logged in. And the biggest one: a licence chart that shows utilisation without showing the euro amount attached to the unused half — 15–25% waste is the normal finding, and a percentage on a slide is a fact while the same number in euros is a decision.

The finished thing is one page a client reads and four they can check. If it takes three hours to assemble every month it will quietly become two pages and then stop, so the sections above are worth wiring to a schedule rather than a person. That is what GraphPaaS does — the same five surfaces, per tenant, collected on a timer and retained past the window Microsoft keeps them in, so the diff column is always there when month-end arrives.

Sources

GraphPaaS

Five sections, every tenant, already collected when month-end arrives.

See it on your tenant →

Newsletter

Get the next article by email

Practical M365 & Azure cost pieces like this one. No spam, unsubscribe anytime.