---
name: microsoft-outlook-oauth-app
description: >-
  Registers a Microsoft Entra ID application for Outlook mail and calendar
  access through Microsoft Graph. Builds on the
  `microsoft-entra-app-registration` base skill; read that first. This skill
  covers only what Outlook adds: the `Mail.*` / `Calendars.*` / `.Shared`
  permissions and their naming traps, why every Outlook application permission
  reaches every mailbox in the tenant and the two ways to narrow that (RBAC
  for Applications and application access policies), shared, room and group
  mailboxes, per-mailbox throttling, change-notification lifetimes, and the
  EWS retirement. Use when asked to get Outlook or Microsoft 365 mail OAuth
  credentials, connect Exchange Online mailboxes or calendars, scope an
  app-only mail connector to some mailboxes, or fix an Outlook connector
  returning `ErrorAccessDenied`, `403` or `MailboxConcurrency` errors. For
  plain Microsoft sign-in use `microsoft-oauth-app`.
---

# Outlook (Microsoft Entra ID) OAuth2 App Registration

Get a working OAuth2 client for Outlook mail and calendar in Exchange Online — an Entra ID app registration with the
right `Mail.*` and `Calendars.*` Graph permissions — for a platform that connects many customers' Microsoft 365
tenants.

Registering the app is the base skill's job and takes twenty minutes. **The delegated-vs-application decision is the
whole negotiation**, and it is what this file is about. In Outlook there is no per-mailbox scope you can request at
consent time: an application permission named `Mail.Read` means *every mailbox in the tenant, including the CEO's*,
and the narrowing happens afterwards, in Exchange Online, by the customer's Exchange administrator. Knowing that —
and knowing the exact cmdlets to hand them — is the difference between a connector a security review approves and one
that gets escalated for a quarter.

## Built on: `microsoft-entra-app-registration`

**Read the base skill first.** It is the source of truth for the Entra mechanics this one assumes:

- **Supported account types** — single tenant vs multitenant vs personal Microsoft accounts, the permission ceilings
  each imposes, and `AADSTS50194` when a single-tenant app is called through `/common`.
- **Redirect URIs** — the Web vs SPA vs public-client platform choice, exact case-sensitive matching, the count and
  length limits, and the Unified.to callback hosts.
- **Delegated vs application permissions, and admin consent** — how the two modes differ, which permissions need an
  admin, and that Microsoft Graph *application* permissions require a **Privileged Role Administrator**, not an
  Application Administrator. Every app-only Outlook connector hits that one.
- **Client secrets and certificates** — the 24-month cap, the **Value** shown exactly once, `AADSTS7000222` on expiry,
  add-then-delete rotation, and the per-cloud authority and Graph hosts (commercial, US Gov L4/L5, China).
- **`offline_access`, publisher verification and the generic AADSTS symptom table** — no `Mail.*` or `Calendars.*`
  scope carries a refresh token of its own; `offline_access` is what gets you one, and an Outlook connector that
  stops working after an hour is almost always missing it.

The base also holds the inputs to collect, the run order, tenant ownership, the credential hand-off and the generic
stop-and-ask conditions. **A reader who loads only this file has none of that** — no portal path, no account-type
decision, no secret handling, no consent troubleshooting — and will produce a half-registered app. Everything below
is Outlook-specific.

## Extra inputs to collect

On top of the base skill's batch:

| Input | Notes |
| --- | --- |
| **Delegated (each user authorizes their own mailbox) or application (app-only, tenant-wide)?** | Blocking product decision — read §1 and §2 first |
| **If application: will the customer scope it, and with what?** | RBAC for Applications, or a legacy application access policy (§2). Needs an Exchange Administrator per customer |
| **Which mailboxes are actually in scope?** | User mailboxes only, or also shared / room / equipment / Microsoft 365 group mailboxes (§3) — each behaves differently |
| **Read-only, read/write, or send?** | `Mail.Send` is a separate permission that neither `Mail.Read` nor `Mail.ReadWrite` implies (§1) |
| **Will the connector subscribe to change notifications?** | Changes the minimum permission and adds a renewal treadmill (§5) |
| **Is anything on the customer side still on EWS?** | It has weeks, not years (Platform state) |

Two base-skill choices have Outlook-specific answers. **Supported account types**: Exchange Online mailboxes live in
work/school tenants, and `Mail.Read.Shared`, `Mail.ReadWrite.Shared` and `Mail.Send.Shared` are documented as *valid
only for work or school accounts* — so the personal-account audiences buy you Outlook.com and cost you the 30-permission
ceiling and the shared-mailbox family. Pick **Multiple Entra ID tenants** unless consumer Outlook.com is genuinely in
scope. And on **reuse vs new registration**: a new client ID is worse here than for most vendors, because a scoped
app-only deployment has customer-side state your platform cannot recreate — every RBAC role assignment and every
application access policy is keyed to the *old* client ID and must be re-created by each customer's Exchange admin.

## Platform state (verified 2026-09-20 — re-verify before trusting)

- **EWS is being switched off right now.** Microsoft begins globally disabling Exchange Web Services in Exchange
  Online in **October 2026**, and EWS is **fully disabled in April 2027**. Any tenant whose `EWSEnabled` is still
  `Null` on **2026-10-01** has it flipped to `False` as the rollout reaches them, blocking EWS for every app in that
  tenant; `EWSAllowedAppIDs` is the tenant-level allow list an Exchange admin can use to keep named app IDs working
  through the phased period. This covers Microsoft's own apps too, and it applies to **Exchange Online only** —
  EWS in on-premises Exchange Server is not retired. If a plan in front of you still touches EWS, that is the finding.
- **The Outlook REST API is gone.** The `https://outlook.office.com/api` v2.0 and beta endpoints were decommissioned
  on **2024-03-31**, returning HTTP 410 for a 60-day grace window and nothing after. Microsoft Graph is the only
  supported surface. Some Outlook developer pages still read as if the direct endpoint were an option for features
  Graph lacks — it is not; treat any such wording as stale.
- **Application access policies are superseded.** Microsoft's guidance is now **RBAC for Applications in Exchange
  Online**, and the `New-ApplicationAccessPolicy` reference carries an explicit notice: *"App Access Policies are
  replaced by Role Based Access Control for Applications… Don't create new App Access Policies as these policies will
  eventually require migration."* Existing policies still work, and the Graph permissions reference still calls them
  "application access policy" and links to an article that now **redirects** to the Exchange RBAC page. No retirement
  date has been published. §2 covers both.
- **The `.All` suffix does not mean what it means elsewhere in Graph.** For mail and calendar, the tenant-wide
  application permission is the *bare* name: application `Mail.Read` is "Read mail in all mailboxes" and application
  `Calendars.Read` is "Read events of all calendars". Worse, `Calendars.Read.All` and `Calendars.ReadWrite.All` are
  **not** tenant-wide calendar permissions at all — they are documented as "Read (and write) all users' **work hours
  and locations**". Requesting `Calendars.Read.All` expecting SharePoint-style breadth gets you a different, narrower
  feature and a connector that reads nothing. §1.
- **`Mail.ReadBasic` now exists in both modes**, and there is *also* an application-only `Mail.ReadBasic.All` with an
  identical published description. Either works app-only; prefer whichever the customer's admin sees in their consent
  list and record which one you actually requested.
- **The per-mailbox Outlook throttles are fixed service limits** — 10,000 requests per 10 minutes, four concurrent
  requests, 150 MB of upload per 5 minutes, per app-ID-and-mailbox pair. They are not adjustable and no support
  ticket raises them. §4.

If the Entra admin center, Exchange Online PowerShell or Graph does not behave like this, stop and report what you
actually see.

## 1. The `Mail.*` and `Calendars.*` families

Outlook access is requested as Microsoft Graph permissions. The two columns are genuinely different permissions with
the same name, and the gap between them is the whole security story:

| Permission | Delegated (signed-in user) | Application (app-only) |
| --- | --- | --- |
| `Mail.ReadBasic` | The user's mail, minus `body`, `previewBody`, attachments, extended properties | **Every mailbox**, same field exclusions |
| `Mail.ReadBasic.All` | — | **Every mailbox**, same field exclusions |
| `Mail.Read` | The user's mailbox | **Every mailbox in the tenant** |
| `Mail.ReadWrite` | Create/read/update/delete the user's mail — **not** send | **Every mailbox** — not send |
| `Mail.Send` | Send as the signed-in user | **Send as any user in the tenant** |
| `Mail.ReadBasic.Shared` / `Mail.Read.Shared` / `Mail.ReadWrite.Shared` / `Mail.Send.Shared` | Mail the user can already reach, including shared and delegated mailboxes (§3) | **Not available** |
| `Calendars.ReadBasic` | The user's events, minus body, attachments, extensions | — (`Calendars.ReadBasic.All` is the app-only form) |
| `Calendars.Read` | The user's calendars | **Every calendar in the tenant** |
| `Calendars.ReadWrite` | Full access to the user's calendars | **Every calendar in the tenant** |
| `Calendars.Read.Shared` / `Calendars.ReadWrite.Shared` | Delegate and shared calendars the user can access | **Not available** |
| `Calendars.Read.All` / `Calendars.ReadWrite.All` | **Work hours and locations only** — not calendar contents | Same, tenant-wide |
| `MailboxSettings.Read` / `.ReadWrite` | The user's mailbox settings (automatic replies, time zone, language) | Every user's mailbox settings |
| `Place.Read.All` | Conference rooms and room lists — **admin consent required even delegated** | Same, admin consent required |

Three things that cost time:

- **Every delegated permission in that table is user-consentable**; every application permission requires an admin —
  and per the base skill, an admin who holds **Privileged Role Administrator**, since these are Graph app roles.
  `Place.Read.All` is the odd one out: it needs admin consent in *both* modes.
- **`Mail.ReadWrite` does not include send, and `Mail.Send` does not include read.** A connector that drafts and
  sends needs both. Conversely `Mail.Send` alone is enough to send *and* save a copy to Sent Items without
  `Mail.ReadWrite`, which is a genuinely narrower ask when sending is all you do.
- **Mixing defeats the point.** Any scope present in the token is honored, so requesting `Mail.Read` alongside
  `Mail.ReadWrite` does not make the app safer — it makes the consent screen longer for no capability, and a reviewer
  reads the widest scope in the list. Requesting a delegated `Mail.Read` *and* an application `Mail.Read` makes the
  app tenant-wide regardless of how the connect flow is described.

Identity-only scopes (`openid`, `profile`, `email`, `offline_access`, `User.Read`) carry no mailbox access at all.

**Product fact — as of 2026-09-20.** Unified.to's Outlook connector offers two connection styles, and the security
conversation is different for each:

- Its **delegated (user-authorizes) OAuth2 flow** authorizes through the `/common` endpoint, sends
  `prompt=select_account`, and requests scopes **per unified object**, always including `openid`, `email`, `profile`
  and **`offline_access`** (so refresh tokens are expected on every non-login flow): `Mail.ReadBasic` to list mail
  folders; `Mail.Read` to read messages; `Mail.Read` + `Mail.Send` + `Mail.ReadWrite` to write and send them;
  `Calendars.Read` for calendars, events and free/busy reads and `Calendars.ReadWrite` for calendar and event writes;
  and for its HR/people objects the tenant-directory scopes `Directory.Read.All`, `User.Read`, `User.Read.All` —
  escalating to `Directory.ReadWrite.All` and `User.ReadWrite.All` for writes. There is also a login-only flow that
  requests just `openid`, `email`, `profile` and `User.Read`.
- Its **app-only (client-credentials) connection** takes a **client ID, client secret and tenant ID** supplied by the
  customer, exchanges them for a token with the literal `https://graph.microsoft.com/.default`, and reads a specific
  mailbox by passing the target user on the request. It therefore carries **whatever application permissions the
  customer's own Entra app was granted** — the connector requests nothing and narrows nothing. All of the scoping in
  §2 is the customer's to do.

Three reactions to expect from that, and to get ahead of. The mail-write set requests `Mail.Read` *and*
`Mail.ReadWrite`, which is redundant — `Mail.ReadWrite` supersedes it — and reviewers notice. The directory scopes
are the escalation trigger: a customer who accepted a mailbox-scoped connector will stop at `Directory.ReadWrite.All`,
which has nothing to do with Outlook, so only enable the objects that need it. And because the delegated flow uses
`/common`, personal Microsoft accounts can reach the consent screen even though the directory scopes are meaningless
for them. **Confirm the current scope set with the connector's owner before you register anything**; scope lists
change and this one is a snapshot.

## 2. Application permissions reach every mailbox — and how to narrow that

This is the section that makes an app-only Outlook connector approvable. Understand it before choosing a permission.

Consenting application `Mail.Read` grants read access to **every mailbox in the tenant, immediately, with no further
step**. There is no `Mail.Selected`. The permission does not become narrower by only calling one mailbox, and a
customer's security team will read the grant, not your code. Narrowing is done **after consent, in Exchange Online,
by an Exchange administrator**, and it is out-of-band work your platform cannot self-serve.

There are two mechanisms. Offer the first; be able to read the second, because most existing tenants run it.

### RBAC for Applications (current, recommended)

Exchange Online's Role Based Access Control for Applications assigns an *application role* to a service principal over
a *resource scope* — a management scope (a recipient filter over mailbox properties) or a Microsoft Entra
administrative unit. Requires membership of **Organization Management** in Exchange Online (Exchange Administrator in
Entra) to assign.

```powershell
# 1. Point Exchange at the app's service principal (get both IDs from Enterprise applications, not App registrations)
New-ServicePrincipal -AppId <client ID> -ObjectId <service principal object ID> -DisplayName "<app name>"

# 2. Define which mailboxes are in scope
New-ManagementScope -Name "Sales mailboxes" -RecipientRestrictionFilter "CustomAttribute1 -eq 'sales'"

# 3. Grant one role over that scope
New-ManagementRoleAssignment -App <service principal object ID> -Role "Application Mail.Read" `
    -CustomResourceScope "Sales mailboxes"

# 4. Prove it
Test-ServicePrincipalAuthorization -Identity "<app name>" -Resource <target mailbox> | Format-Table
```

- **The roles are named `Application <permission>`** — `Application Mail.Read`, `Application Mail.ReadBasic`,
  `Application Mail.ReadWrite`, `Application Mail.Send`, `Application MailboxSettings.Read`/`.ReadWrite`,
  `Application Calendars.Read`, `Application Calendars.ReadWrite`, `Application Contacts.Read`/`.ReadWrite`, plus
  bundles (`Application Mail Full Access`, `Application Exchange Full Access`) and `Application EWS.AccessAsApp`.
  Pick the weakest that works; the bundles exist to be refused.
- **`-RecipientAdministrativeUnitScope <AU id>`** replaces `-CustomResourceScope` when the customer already organizes
  mailboxes into administrative units. Microsoft recommends the native AU parameter over a scope that merely points
  at one.
- **The trap that makes this pointless:** RBAC assignments are **a union with** the Entra grant, not a filter on it.
  If the app still holds an unscoped `Mail.Read` in Entra ID, a resource-scoped `Mail.Read` in Exchange adds nothing
  and the app still reads every mailbox. Microsoft says so explicitly. **The Entra application permission must be
  removed** for the RBAC scope to mean anything — which means the run order is: consent, verify, then *un*-consent.
  Say that out loud to the admin; it is counter-intuitive and it is where scoping silently fails.
- **Changes are cached for 30 minutes to 2 hours** depending on how recently the app called.
  `Test-ServicePrincipalAuthorization` bypasses the cache; a live call does not. Do not conclude a grant failed
  inside that window.
- Other limits worth knowing: **10,000 apps per organization**; exclusive management scopes do not restrict app
  access; application roles cannot be copied, derived, or placed in role groups; and **Autodiscover does not work**
  when using RBAC application roles.

### Application access policies (legacy, still everywhere)

```powershell
New-ApplicationAccessPolicy -AccessRight RestrictAccess -AppId "<client ID>" `
    -PolicyScopeGroupId "connector-mailboxes@contoso.com" `
    -Description " "
```

- **`RestrictAccess`** limits the app to the members of the named group; **`DenyAccess`** does the opposite, and
  a `DenyAccess` policy always wins over a `RestrictAccess` policy for the same app-and-mailbox pair. **If no policy
  matches the app at all, access is granted** — a policy that was never created is not a deny.
- **`-PolicyScopeGroupId` only accepts security principals**: user mailboxes, mail users, and **mail-enabled security
  groups**. It explicitly rejects distribution groups, dynamic distribution groups, Microsoft 365 groups, mail
  contacts, mail-enabled public folders, discovery mailboxes, **room and equipment mailboxes, and shared mailboxes**.
  To scope to a shared or room mailbox you must add it as a **member of a mail-enabled security group** and point the
  policy at the group. This is the single most common reason a run stalls.
- Unlike RBAC, these policies *do* constrain the permissions granted in Entra ID — that is exactly what they are for,
  and why they still exist.
- Capacity is measured in bytes, not policies: roughly **300 policies** if each description is a single space
  character, about 100 otherwise, then `The total size of App Access Policies exceeded the limit`.
- They apply to Graph **and** EWS, which is why they show up in old runbooks. Do not create new ones; migrate the
  scoping group into a management scope with a `MemberOfGroup` filter on the group's distinguished name, assign the
  RBAC role, then remove the Entra consent and the policy. Nested group members are out of scope either way.

### Delegated is the escape hatch

If each end user authorizes their own mailbox, none of the above is needed: a delegated token can never exceed what
that user can already do, and there is no tenant-wide grant to defend. That is Microsoft's own recommendation and it
is the fastest path through a security review. Use application permissions only when there is genuinely no user —
scheduled sync of a shared mailbox, a room-booking integration, ingestion from mailboxes nobody signs into.

## 3. Shared, delegated, room and group mailboxes

Which mailbox kinds a connector can reach depends entirely on which permission model it uses.

| Mailbox kind | Delegated | Application |
| --- | --- | --- |
| The signed-in user's own | `Mail.*` / `Calendars.*` | Reachable as `/users/{id}` |
| **Shared mailbox** (no sign-in, usually unlicensed) | Only via a licensed user who holds Full Access, using the `.Shared` permissions | Reachable as `/users/{shared-mailbox-id}`, like any other |
| **Delegated mailbox / shared folder** | `.Shared` permissions, addressed as `/users/{owner}/mailFolders(...)` | Reachable directly |
| **Room / equipment mailbox** | `Calendars.Read.Shared` where the room's calendar is shared; `Place.Read.All` for room and room-list metadata (admin consent) | `Calendars.Read` / `Calendars.ReadWrite`; the documented RBAC example is exactly a room-booking app scoped by region |
| **Microsoft 365 group mailbox** | Group conversation/thread/post APIs, not the `Mail.*` family | Same; generic group-mailbox item CRUD is confirmed as **never** coming to Graph |

The traps:

- **A shared mailbox cannot sign in**, so it can never produce a delegated token of its own. The delegated path
  requires a *licensed* user with Full Access to it, and the app inherits that user's reach — if their access is
  removed, the connection dies with a `403` and nothing in your logs says why.
- **`.Shared` is delegated-only and work/school-only.** There is no `Mail.Read.Shared` application permission, and it
  is not valid for personal Microsoft accounts.
- **`.Shared` permissions cannot subscribe to change notifications.** Microsoft's guidance is blunt: to subscribe to
  messages in a shared, delegated, or any other user's folder, use the **application** permission `Mail.Read`. So a
  connector that wants webhooks on a shared mailbox is pushed from a user-consentable delegated permission to a
  tenant-wide app-only one — which is precisely when §2 stops being optional. §5.
- **Addressing is `/users/{id | userPrincipalName}/...`**, not `/me/...`, for anything but the signed-in user's own
  mailbox. If the owner has not shared or delegated the folder, the same GET returns an error rather than an empty
  list, so "it returns 403 for one user and works for another" is a sharing problem, not a scope problem.
- **Room and shared mailboxes cannot be named directly in an application access policy** (see §2) — they go in a
  mail-enabled security group first.

## 4. Throttling and mailbox concurrency

This is where Outlook connectors fail in production rather than at registration.

- **The limits are per app ID *and* mailbox pair**, not per tenant and not per app. Exceeding them against one mailbox
  does not affect another. Per pair, on both `v1.0` and `beta`:
  **10,000 API requests per 10-minute period**, **four concurrent requests**, and **150 MB of upload (PATCH/POST/PUT)
  per 5-minute period**. Treat all three as fixed service limits.
- **Four concurrent is the number that bites.** A parallel backfill over one large mailbox hits
  `ApplicationThrottled` / `MailboxConcurrency` long before it hits the 10,000-request budget. Bound your worker pool
  per mailbox at four, not per tenant.
- **JSON batching does not buy concurrency.** Microsoft Graph sends the Outlook service at most four requests from an
  unordered batch at a time regardless of which mailboxes they target; a batch using `dependsOn` is executed strictly
  sequentially. Batching saves round trips, not quota.
- **Above all of this sits the global Graph ceiling of 130,000 requests per 10 seconds per app across all tenants.**
  A multitenant connector can trip that without any single customer noticing anything.
- Throttled calls return **HTTP 429** with **`Retry-After`**. Honor it; retrying early still counts against quota.
- **Use delta queries for incremental mail and calendar sync** rather than repeated `$filter` scans, and `$select`
  only the fields you map — the message body is the expensive part of every page.
- **Sending has a separate, non-Graph budget.** Exchange Online enforces a per-mailbox **recipient rate limit of
  10,000 recipients per 24 hours** and a **message rate limit of 30 messages per minute**, plus a tenant-wide
  external-recipient limit (TERRL) that scales with license count and is capped at 5,000/day on trial tenants. These
  are hard service limits that cannot be raised, they apply to mail sent through Graph, and Microsoft states plainly
  that Exchange Online is not suited to bulk mailing. A connector that sends on customers' behalf should surface
  these, not absorb them.

## 5. Change notifications — only if the connector subscribes

Webhooks change which permission you must request, so settle this before the registration, not after.

- **Maximum subscription lifetime for Outlook `message`, `event` and `contact` is 10,080 minutes (under seven days)**
  — and only **1,440 minutes (under one day)** for rich notifications that include resource data. Anything under 45
  minutes is silently rounded up to 45. Renewal is your job; there is no auto-renew, and an expired subscription is
  simply gone.
- Set a **`lifecycleNotificationUrl`** and handle `subscriptionRemoved`, `reauthorizationRequired` and `missed`.
  Reauthorization is how Graph tells you a token behind a long-lived subscription needs refreshing — which again
  depends on `offline_access` having been requested at registration.
- The notification endpoint must be **public HTTPS**. Graph expects a `2xx` within **3 seconds** or it retries for up
  to **4 hours** (retries get a 10-second timeout), and subscription creation is gated on echoing back a validation
  token in plain text.
- `includeResourceData: true` requires an **encryption certificate** on the subscription — a second credential with
  its own expiry, on top of the client secret from the base skill. Diary it the same way.
- **Delegated `.Shared` permissions cannot subscribe at all** (§3), and app-only mail subscriptions need application
  `Mail.Read`. If webhooks are a product requirement on shared mailboxes, the connector is an app-only connector and
  §2 is mandatory.

## 6. Verify the Outlook-specific parts

Run the base skill's end-to-end check first (second tenant, refresh token present, token claim inspection). Then:

1. **Read one message and one event**, not just `/me`. `GET /me/messages?$top=1` and `GET /me/events?$top=1`
   delegated; `GET /users/{upn}/messages?$top=1` app-only.
2. **For app-only, prove the scoping works in both directions**: a mailbox inside the RBAC scope or policy group
   succeeds, and one outside it returns `403`. If the out-of-scope mailbox succeeds, the unscoped Entra grant is still
   in place (§2) — that is the failure this whole file exists to prevent. Allow for the 30-minute-to-2-hour cache, and
   use `Test-ServicePrincipalAuthorization` to check the intent without it.
3. **If sending is in scope**, send one real message and confirm the copy lands in Sent Items.
4. **If webhooks are in scope**, create a subscription, confirm the validation handshake, then renew it once before
   expiry rather than assuming renewal works.

| Symptom | Cause |
| --- | --- |
| App-only calls succeed against mailboxes the customer thought were out of scope | The unscoped Entra application permission is still granted; RBAC is a union, not a filter (§2) |
| `New-ApplicationAccessPolicy` rejects the mailbox you want to scope to | Shared, room and equipment mailboxes aren't valid security principals — put them in a mail-enabled security group (§2) |
| Scoping change made, behavior unchanged for an hour or two | Exchange caches app permissions for 30 minutes to 2 hours (§2) |
| Delegated calls to a shared mailbox return `403` for one user only | That user lost Full Access on the mailbox; `.Shared` inherits the user's reach (§3) |
| Subscription creation fails on a shared or delegated folder | `.Shared` permissions can't subscribe; needs application `Mail.Read` (§3, §5) |
| `Calendars.Read.All` consented, connector still reads no events | `Calendars.Read.All` is work-hours-and-locations, not calendar contents — the tenant-wide read is application `Calendars.Read` (§1) |
| Messages read fine, `sendMail` returns `ErrorAccessDenied` | `Mail.ReadWrite` doesn't imply `Mail.Send` (§1) |
| Message bodies and attachments come back empty or missing | `Mail.ReadBasic` / `Calendars.ReadBasic` exclude body, previewBody, attachments and extended properties by design (§1) |
| `429` with `MailboxConcurrency` / `ApplicationThrottled` on one mailbox only | The four-concurrent-request per app-and-mailbox limit; batching won't help (§4) |
| Bulk send stalls or is deferred after a few thousand messages | Exchange Online recipient/message rate limits, not Graph throttling — not raisable (§4) |
| Webhooks stop after a week with no error | Outlook subscriptions max out under seven days and must be renewed (§5) |
| `HTTP 410 Gone`, or no response at all, from `outlook.office.com/api` | Outlook REST v2.0/beta, decommissioned 2024-03-31 (Platform state) |
| EWS calls start failing tenant-wide with no code change | `EWSEnabled` flipped to `False` in the October 2026 rollout (Platform state) |

## Hand off — the Outlook additions

The base skill's hand-off list applies. Add to the closing summary: whether the connector is **delegated or
application**, and for application — **which mailboxes are in scope, by what mechanism (RBAC management scope /
administrative unit / application access policy), who configured it, and confirmation that the unscoped Entra grant
was removed**. Also: the exact `Mail.*` / `Calendars.*` strings requested; whether `Mail.Send` is included; whether
change-notification subscriptions exist, their renewal cadence and the **encryption certificate's expiry** if resource
data is included; and any EWS dependency still outstanding against the October 2026 / April 2027 dates.

## Stop and ask

On top of the base skill's conditions, hand back to a human when:

- The choice between delegated and application permissions has not been made. It is a product and security decision
  with an onboarding cost attached, not a config detail — and for a mail connector it is *the* decision.
- An application permission is being requested and nobody has agreed who will scope it in Exchange Online, or no one
  in the room holds Exchange Administrator / Organization Management.
- Removing the unscoped Entra grant is required for scoping to take effect (§2) and the app is already live for that
  customer — there is a window where access changes shape, and it needs a plan.
- `Application Mail Full Access` or `Application Exchange Full Access` is proposed, or `Mail.Send` as an application
  permission: "send as any user in the tenant" is a phishing primitive and reviewers treat it as one.
- The design depends on EWS, on `outlook.office.com/api`, on generic Microsoft 365 group or public-folder mailbox CRUD,
  or on discovery-mailbox access. Those are retired or confirmed as never coming to Graph; the plan needs rewriting,
  not a workaround.
- A customer asks for the per-mailbox throttles or the Exchange sending limits to be raised. They cannot be.
- New application access policies are being created rather than RBAC role assignments, and nobody has weighed the
  eventual migration.
- Personal Microsoft accounts (Outlook.com) are in scope alongside work/school mailboxes — that changes the account
  type, the permission ceiling, and removes the entire `.Shared` family (§1 and the base skill's §3).

## References

The base skill carries the shared Entra references. These are the Outlook-specific ones; verified 2026-09-20, every
link returned HTTP 200.

- Microsoft Graph permissions reference (`Mail.*`, `Calendars.*`, `MailboxSettings.*`, `Place.*`) — https://learn.microsoft.com/en-us/graph/permissions-reference
- Role Based Access Control for Applications in Exchange Online — https://learn.microsoft.com/en-us/exchange/permissions-exo/application-rbac
- `New-ApplicationAccessPolicy` (legacy scoping, with the replacement notice) — https://learn.microsoft.com/en-us/powershell/module/exchangepowershell/new-applicationaccesspolicy
- `Test-ServicePrincipalAuthorization` — https://learn.microsoft.com/en-us/powershell/module/exchangepowershell/test-serviceprincipalauthorization
- Outlook mail API overview — https://learn.microsoft.com/en-us/graph/api/resources/mail-api-overview
- Outlook calendar overview — https://learn.microsoft.com/en-us/graph/outlook-calendar-concept-overview
- Get messages in a shared or delegated folder — https://learn.microsoft.com/en-us/graph/outlook-share-messages-folders
- `place` resource (rooms and room lists) — https://learn.microsoft.com/en-us/graph/api/resources/place
- Send mail (`sendMail`) — https://learn.microsoft.com/en-us/graph/api/user-sendmail
- Get incremental changes to messages (delta) — https://learn.microsoft.com/en-us/graph/delta-query-messages
- Microsoft Graph service-specific throttling limits (Outlook service limits) — https://learn.microsoft.com/en-us/graph/throttling-limits
- Microsoft Graph throttling guidance — https://learn.microsoft.com/en-us/graph/throttling
- Exchange Online limits (recipient and message rate limits) — https://learn.microsoft.com/en-us/office365/servicedescriptions/exchange-online-service-description/exchange-online-limits
- Troubleshoot outbound sending limits — https://learn.microsoft.com/en-us/defender-office-365/outbound-spam-sending-limits-troubleshoot
- Change notifications overview — https://learn.microsoft.com/en-us/graph/change-notifications-overview
- `subscription` resource (maximum lifetimes per resource) — https://learn.microsoft.com/en-us/graph/api/resources/subscription
- Change notification delivery via webhooks (validation, retries) — https://learn.microsoft.com/en-us/graph/change-notifications-delivery-webhooks
- Reduce missed subscriptions and notifications (lifecycle events) — https://learn.microsoft.com/en-us/graph/change-notifications-lifecycle-events
- Deprecation of Exchange Web Services in Exchange Online (timeline) — https://learn.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/deprecation-of-ews-exchange-online
- Exchange Online EWS, Your Time is Almost Up (disablement process) — https://techcommunity.microsoft.com/blog/exchange/exchange-online-ews-your-time-is-almost-up/4492361
- EWS to Microsoft Graph API mapping — https://learn.microsoft.com/en-us/graph/migrate-exchange-web-services-api-mapping
- EWS usage reports — https://learn.microsoft.com/en-us/microsoft-365/admin/activity-reports/ews-usage
- Outlook REST API v2.0 and beta endpoint deprecation — https://devblogs.microsoft.com/microsoft365dev/outlook-rest-api-v2-0-deprecation-notice/
