To let an application send mail through Microsoft Graph from only a few mailboxes, do not grant the tenant-wide Mail.Send application permission in Microsoft Entra ID. Instead, create a pointer to the app's service principal in Exchange Online with New-ServicePrincipal, define the allowed mailboxes with a management scope or administrative unit, and assign the Application Mail.Send role with New-ManagementRoleAssignment. Verify the result with Test-ServicePrincipalAuthorization, and make sure no unscoped Mail.Send grant remains in Entra ID, because the two permission systems add together.
Who this is for and what you will have
This guide is for Exchange Online and Microsoft Entra administrators who look after daemon apps, scripts and line-of-business systems that send notification mail without a signed-in user. It is especially relevant if you are moving an app from Exchange Web Services or SMTP to Microsoft Graph and want it to stop having access to every mailbox in the tenant along the way.
At the end you will have:
- An Exchange Online service principal pointer for the app.
- A management scope that contains only the mailboxes the app may send as.
- An
Application Mail.Sendrole assignment limited to that scope. - No tenant-wide
Mail.Sendconsent left in Microsoft Entra ID. - A repeatable test that proves the app can send from an allowed mailbox and not from any other.
Scoping app permissions is a direct application of least privilege; the zero trust remote access architecture covers the wider principles.
How RBAC for Applications works
When an app uses the OAuth 2.0 client credentials flow, it acts with its own identity. A Mail.Send application permission consented in Microsoft Entra ID lets the app send mail as any user in the organization. There is no way to narrow that grant inside Entra ID itself.
RBAC for Applications extends the Exchange Online RBAC model to apps. You assign an application role to a service principal and pair it with a resource scope that lists the mailboxes the app can act on. Microsoft describes these grants as independent of the unscoped grants in Entra ID, and the key rule is that the two are combined as a union:
| Where the permission lives | What it covers | Can it be scoped? |
|---|---|---|
Microsoft Entra ID application permission (Mail.Send) | Every mailbox in the tenant | Only through the legacy Application Access Policies |
| Application Access Policy (legacy) | Restricts the Entra ID grant to a group | Yes, but the feature is being replaced |
| RBAC for Applications role assignment | Only mailboxes in the assigned scope | Yes, by management scope or administrative unit |
If the app keeps an unscoped Mail.Send from Entra ID and also gets a scoped Application Mail.Send assignment in Exchange, the result is unscoped access. The scoped grant only restricts the app when it is the only source of the permission.
The supported application roles map one-to-one to Microsoft Graph permissions. The ones most relevant to a mail-sending app are:
| Role name | Graph permission | Notes |
|---|---|---|
Application Mail.Send | Mail.Send | Send mail as users in scope |
Application Mail.ReadWrite | Mail.ReadWrite | Create, read, update and delete mail; no send |
Application Mail Full Access | Mail.ReadWrite, Mail.Send | Combined read/write and send |
Application SMTP.SendAsApp | SMTP.SendAsApp | SMTP client submission with app identity |
Application EWS.AccessAsApp | EWS.AccessAsApp | EWS with full access to mailboxes in scope |
For a notification sender, Application Mail.Send is enough. Choose Application Mail Full Access only if the app must also read replies or manage items in the same mailboxes.
Prerequisites
- Membership of the Organization Management role group in Exchange Online, which holds the delegating assignment for the application roles. Microsoft also states that in Microsoft Entra ID you need the Exchange Administrator role to assign these permissions.
- The ExchangeOnlineManagement module, connected with
Connect-ExchangeOnline. - Microsoft Graph PowerShell with rights to read and change the app's permissions. Microsoft's guidance for revoking application permissions requires at least the Cloud Application Administrator role.
- An app registration that authenticates with the client credentials flow, plus the mailbox or mailboxes it should send from, for example
notifications@contoso.com.
Connect to both services:
Connect-ExchangeOnline -UserPrincipalName admin@contoso.com
Connect-MgGraph -Scopes 'Application.ReadWrite.All','AppRoleAssignment.ReadWrite.All'Step 1: Collect the app's IDs
New-ServicePrincipal needs two values: the application (client) ID and the object ID of the service principal. Take both from Entra ID > Enterprise apps > All applications > your app. Microsoft warns not to use the IDs on the App registrations page, because the object ID there belongs to the application object, not to the service principal.
You can read the same values with Graph PowerShell:
$appId = '11111111-2222-3333-4444-555555555555'
$sp = Get-MgServicePrincipal -Filter "appId eq '$appId'"
$sp | Format-List DisplayName, AppId, IdThe Id property is the service principal object ID.
Step 2: Define which mailboxes are in scope
You have two scoping options: an Exchange management scope, which uses a recipient filter, or a Microsoft Entra administrative unit. A filter on group membership is the easiest to maintain for a small set of sending mailboxes.
Create a mail-enabled security group and add the sending mailboxes as direct members:
New-DistributionGroup -Name 'App-MailSend-Notifications' -Type 'Security'
Add-DistributionGroupMember -Identity 'App-MailSend-Notifications' -Member 'notifications@contoso.com'The MemberOfGroup filter needs the group's distinguished name, which you can read with Get-Group. Then create the scope:
$dn = (Get-Group -Identity 'App-MailSend-Notifications').DistinguishedName
New-ManagementScope -Name 'Scope-App-MailSend-Notifications' `
-RecipientRestrictionFilter "MemberOfGroup -eq '$dn'"Two limits apply to group-based scopes. Only direct members count: members of a nested group are out of scope. Microsoft 365 Groups, mail-enabled security groups and distribution lists are supported as the scoping group.
Other filter properties work too. Microsoft's own example scopes mailboxes with a custom attribute:
New-ManagementScope -Name 'Canadian users' -RecipientRestrictionFilter "CustomAttribute1 -eq '012332'"Do not rely on exclusive scopes to block an app: Microsoft lists "Exclusive management scopes don't restrict app access" as a limitation.
Step 3: Create the service principal pointer
The service principal in Exchange Online is only a pointer to the one in Entra ID. You can't create service principals with Exchange tools, and Exchange rejects pointers to objects that don't exist.
New-ServicePrincipal -AppId $sp.AppId -ObjectId $sp.Id -DisplayName 'Contoso Notification Service'If someone later deletes the service principal in Entra ID, Exchange removes the pointer and its role assignments automatically. Management scopes are left in place.
Step 4: Assign the Application Mail.Send role
Assign the role to the service principal and limit it to the scope:
New-ManagementRoleAssignment -Name 'Notifications-MailSend' `
-App $sp.Id `
-Role 'Application Mail.Send' `
-CustomResourceScope 'Scope-App-MailSend-Notifications'The -App parameter takes the service principal object ID. If you prefer an administrative unit, replace -CustomResourceScope with -RecipientAdministrativeUnitScope and the administrative unit ID. The cmdlet also accepts -RecipientGroupScope, which treats the individual members of a group (again, not nested groups) as in scope without a separate management scope.
To change the scope later, use Set-ManagementRoleAssignment with a new -CustomResourceScope or -RecipientAdministrativeUnitScope.
Step 5: Remove the unscoped grant from Entra ID
This step is what actually restricts the app. If Mail.Send was consented as an application permission, revoke it.
In the portal:
- Sign in to the Microsoft Entra admin center as at least a Cloud Application Administrator.
- Browse to Entra ID > Enterprise apps > All applications and select the app.
- Select Permissions, then the Admin consent tab.
- Select the ... control next to the Microsoft Graph
Mail.Sendpermission and choose Revoke permission.
With Graph PowerShell, list the app role assignments held by the service principal and remove only the one for Mail.Send:
$graph = Get-MgServicePrincipal -Filter "appId eq '00000003-0000-0000-c000-000000000000'"
$mailSend = $graph.AppRoles | Where-Object { $_.Value -eq 'Mail.Send' }
Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $sp.Id -All |
Where-Object { $_.AppRoleId -eq $mailSend.Id } |
ForEach-Object {
Remove-MgServicePrincipalAppRoleAssignedTo -ServicePrincipalId $graph.Id -AppRoleAssignmentId $_.Id
}Microsoft notes that revoking a permission doesn't stop someone consenting to it again. Remove Mail.Send from the API permissions of the app registration too, so a future admin consent click does not bring the tenant-wide grant back.
Step 6: Verify
Test the authorization
Test-ServicePrincipalAuthorization simulates what the RBAC assignments allow. With -Resource it also reports whether a given mailbox is in scope:
Test-ServicePrincipalAuthorization -Identity $sp.Id -Resource 'notifications@contoso.com' | Format-Table
Test-ServicePrincipalAuthorization -Identity $sp.Id -Resource 'ceo@contoso.com' | Format-TableThe output lists RoleName, GrantedPermissions, AllowedResourceScope, ScopeType and InScope. Expect InScope to be True for the notification mailbox and False for everyone else. If you omit -Resource, InScope shows "Not Run". The test excludes permissions granted in Entra ID, so it can look correct while an unscoped Entra ID grant still exists; that is why step 5 matters.
Send a real message
From the app, call the Graph sendMail action against an in-scope mailbox:
{
"message": {
"subject": "Scoped send test",
"body": { "contentType": "Text", "content": "Sent by the notification service." },
"toRecipients": [ { "emailAddress": { "address": "admin@contoso.com" } } ]
},
"saveToSentItems": false
}Post it to https://graph.microsoft.com/v1.0/users/notifications@contoso.com/sendMail. A successful call returns 202 Accepted, which means the request was accepted, not that delivery finished. Repeat the call against a mailbox outside the scope; it should be refused.
Allow for caching. Microsoft documents that app permission changes are cached for 30 minutes to 2 hours depending on recent usage; an app with no inbound calls has its cache reset after 30 minutes, and an active app can keep the old result for up to 2 hours. The test cmdlet bypasses this cache, so it can show the new state before the app does.
Migrating from an Application Access Policy
If the app is currently restricted by New-ApplicationAccessPolicy, Microsoft's documented migration reuses the same group and causes no interruption:
- Create a management scope that points to the policy's scoping group with a
MemberOfGroupfilter, as in step 2. - Create the service principal pointer.
- Assign the needed application role with the management scope.
- Remove the consented permission in Entra ID.
- Remove the Application Access Policy.
Keep the order. Removing the Entra ID consent before the Exchange assignment exists leaves the app without permission for the duration of the cache.
Notes for apps leaving EWS
An app that used EWS with full_access_as_app had access to every mailbox. When you rewrite it for Microsoft Graph, don't carry that tenant-wide access over; the rewrite is the natural moment to switch to scoped roles. RBAC for Applications supports both Microsoft Graph and EWS, and the Application EWS.AccessAsApp role can scope EWS access while the app is still being migrated, but the Autodiscover service can't currently be accessed when using RBAC application roles. Microsoft has announced the retirement of EWS in Exchange Online, so treat scoped EWS access as a temporary bridge rather than a destination.
Troubleshooting
The app can still send as mailboxes outside the scope. An unscoped Mail.Send grant still exists in Entra ID, or an Application Access Policy plus Entra ID grant covers those mailboxes. Permissions from both systems are additive. Check Permissions > Admin consent on the enterprise app and remove the grant.
InScope is False for a mailbox that should be allowed. Confirm that the mailbox is a direct member of the scoping group, not a member of a nested group, and that the filter in the scope uses the group's current distinguished name. Run Get-ManagementScope -Identity 'Scope-App-MailSend-Notifications' and compare the RecipientFilter.
The test passes but the app still fails, or still succeeds where it shouldn't. Wait for the cache window of up to 2 hours before you draw conclusions.
The app gets access denied for every mailbox after the change. The Entra ID grant was removed but the Exchange role assignment is missing, points to the wrong object ID, or targets a different role. Check with Get-ManagementRoleAssignment -Role 'Application Mail.Send' and confirm the -App value is the service principal object ID, not the application object ID from App registrations.
Autodiscover calls fail. This is a documented limitation of RBAC application roles. Configure the app with a fixed endpoint instead of Autodiscover.
You can't add the app to a role group. Applications can't be role group members, and application roles can't be copied or derived. Assign roles directly to the service principal.
Checklist
- Service principal IDs taken from Enterprise apps, not App registrations.
- Sending mailboxes are direct members of the scoping group, or filtered by an attribute you control.
New-ServicePrincipalpointer created in Exchange Online.Application Mail.Sendassigned with-CustomResourceScopeor-RecipientAdministrativeUnitScope.- Tenant-wide
Mail.Sendrevoked in Entra ID and removed from the app registration's requested permissions. Test-ServicePrincipalAuthorizationshowsInScope = Trueonly for intended mailboxes.- A real
sendMailcall tested from both an in-scope and an out-of-scope mailbox after the cache window. - Any legacy Application Access Policy for the app removed after the migration.
References
- Role Based Access Control for Applications in Exchange Online
- Application Access Policies (legacy)
- New-ManagementRoleAssignment
- New-ManagementScope
- Filterable properties for the RecipientFilter parameter
- user: sendMail - Microsoft Graph v1.0
- Review permissions granted to enterprise applications
- New-DistributionGroup
- Add-DistributionGroupMember
- Grant or revoke API permissions programmatically