Skip to main content
Vexa takes minutes by being invited to meetings like a colleague. That requires one mailbox in your tenant and one application identity that can read only that mailbox. This page is the administrator’s side of it: about twenty minutes, once, from a terminal.
Not shipped. These are the tenant-side steps, and they were executed against a live Microsoft 365 tenant before they were written down. The Vexa side is not there yet: the Graph transport is an open pull request against a feature branch, it is fixture-proven and has never met a live Exchange server, and the chart does not plumb these variables — see the mail edge. This page exists so your identity team can start the approval path, which is the long pole.
The <ANGLE_BRACKET> values are the only things you change.

What you are creating

The Exchange ApplicationAccessPolicy is not optional. Graph application permissions are tenant-wide by default: without that policy, Mail.ReadWrite means every mailbox in your organisation. With it, the same credential can read exactly one. Skip it and no security review will pass — nor should it.

1. The mailbox

Create a normal user with a licence that includes Exchange Online and Microsoft Teams (Business Basic is enough). Nothing about it is special except its name.
Record two values — you need both later, and they are not interchangeable:

2. The app registration

3. The four application permissions

These are application permissions (app-only, no signed-in user), not delegated ones. The role ids are the same in every tenant, but you never have to trust that — ask Graph:
Then grant admin consent — a Global Administrator or Privileged Role Administrator must do this, and nothing works until it is done:
az ad app permission grant is not the same thing and is not enough here: it records a delegated grant, while application roles require admin-consent. If a later call returns Access is denied. Check credentials and try again., consent is the first thing to re-check.

4. Scope the app to one mailbox — Exchange ApplicationAccessPolicy

Create a mail-enabled security group containing only the Minutes mailbox, then bind the app to it. Everything outside the group becomes invisible to that credential.
Verify it — and verify it against a mailbox that should be denied, not only the one that should be allowed. A policy that grants correctly and restricts nothing looks identical from the allowed side:
Policy changes take a few minutes to propagate; a Denied that later becomes Granted means you tested too early.

5. Let the app create Teams meetings

Teams gates application-permission meeting creation separately, with its own policy:
Allow up to 30 minutes for this one to take effect. Until it does, meeting creation returns Forbidden even though every permission is consented — a genuinely confusing state, so check the clock before you check your work.

6. The secret

This value is printed once. It goes from your terminal straight into the station’s Kubernetes Secret and travels nowhere else — not through email, not through chat, not to Vexa. Nobody at Vexa needs to see it, and nobody at Vexa should be sent it.
Set a calendar reminder for the expiry. An expired client secret fails as a flat invalid_client with no warning window.

7. Prove it works

Four checks, in order. Each one fails distinctly, which is the point of doing them separately.
Delete the meeting from check (d) when you are done: DELETE /v1.0/users/$MAILBOX_ID/onlineMeetings/<id>.

Gotchas, in the order they bite

POST /users/minutes@your-domain/onlineMeetings fails with a message about the value not being a valid GUID or the segment not being found. It reads like a malformed request; it is just the wrong identifier. /users/{objectId} is required on this endpoint even though the UPN works fine on /users/{upn}/messages. Keep the GUID from step 1 next to the UPN.
Testing while signed in as yourself proves your own admin rights, not the app’s. It will happily read every mailbox and create every meeting, and tell you nothing about whether the ApplicationAccessPolicy works. Always test with a client-credentials token as in step 7.
They are different systems with similarly named cmdlets. Mail works and meetings return Forbidden → the Teams New-/Grant-CsApplicationAccessPolicy step is missing or has not propagated yet (up to 30 minutes).
Exchange suppresses self-delivery: if the Minutes mailbox is both organiser and attendee, the event is written straight to its calendar and no invitation is delivered. When you test the flow end to end, invite the Minutes mailbox from a different account. An empty inbox here is Exchange behaving as designed, not Vexa failing to receive.
az ad app credential reset defaults to a short lifetime unless you pass --years. Expiry surfaces as invalid_client with no prior warning. Put the date in a calendar.

What Vexa can and cannot see

To revoke everything at once, in one command: