Symfony Mailer: MS Graph (App + Delegated)
This module provides two ways to send emails using Microsoft Graph API: as a shared mailbox for automated messages, or as a specific user's mailbox after they authorize it. You can choose the method that best suits your needs and even use both simultaneously for different types of emails.
The Symfony Mailer MS Graph module adds a Microsoft Graph API mail transport for the Symfony Mailer module, supporting two independent authentication methods: Application (client credentials) and Delegated (OAuth 2.0 authorization code).
- Application (client credentials): mail is sent as a shared mailbox using app-only permissions — no user login required, ideal for automated/system email.
- Delegated (OAuth 2.0 authorization code): mail is sent as a specific Microsoft 365 mailbox that authorizes this app once via a Microsoft OAuth2 sign-in, rather than as a generic service account; a refresh token keeps the authorization alive, renewed automatically via cron.
Choose whichever method fits your use case, or run both side by side using Symfony Mailer's own transport policy system.
Compatibility
Symfony Mailer 1.x and 2.x use different, incompatible transport plugin systems, so this module's branch must match the Symfony Mailer version installed on your site:
- Symfony Mailer 1.x → use this module's 1.x branch.
- Symfony Mailer 2.x → use this module's 2.x branch.
Installing the wrong branch for your Symfony Mailer version will prevent the transport plugins from being discovered.
Configuration
Application method
When to use this: choose Application when you want to send mail as a shared/system mailbox with no end-user sign-in involved — automated notifications, transactional email, cron-triggered mail, or any case where there's no specific person's mailbox to authorize against. It's simpler to set up than Delegated since there's no OAuth consent step or refresh token to maintain.
- In Microsoft Entra: grant the app registration the Application permission
Mail.Send, and have an admin grant consent. - In Drupal: go to Administration » Configuration » System » Mailer transport and edit the "Microsoft Graph (Application)" transport (created automatically when the module is installed).
- Enter the shared mailbox's Email address, Client ID, and Tenant ID; select the Key entity holding the client secret (or leave the default, which reads the
OAUTH2_APPLICATION_CLIENT_SECRETenvironment variable). Save. - Set this transport as the default transport in Symfony Mailer's configuration if you want all site emails to use it. Alternatively, attach it to a Mailer Policy to route only specific email types through this transport.
- Default Transport – All site emails will be sent through this transport.
- Mailer Policy – Only the selected email types will be sent through this transport.
Delegated method
When to use this: choose Delegated when mail needs to look like it's genuinely coming from a specific person's Microsoft 365 mailbox — for example, a named staff mailbox that authorizes the app once via sign-in, rather than a generic system account. It requires an initial OAuth consent step and ongoing (automatic) refresh token renewal, in exchange for that per-mailbox authenticity.
- In Microsoft Entra: grant the app registration the Delegated permission
Mail.Send, and add theoffline_accessscope (required to receive a refresh token). - In Drupal: go to Administration » Configuration » System » Mailer transport and edit the "Microsoft Graph (Delegated)" transport (created automatically when the module is installed).
- Enter the mailbox's Email address, Client ID, and Tenant ID; select the Key entity holding the client secret (or leave the default, which reads the
OAUTH2_CLIENT_SECRETenvironment variable). Save. - Copy the Redirect URI shown on this same form into the Entra app registration's Authentication settings.
- Still on this form, click "Authorize with Microsoft" and sign in as the mailbox that should send mail.
- Set this transport as the default transport in Symfony Mailer's configuration if you want all site emails to use it. Alternatively, attach it to a Mailer Policy to route only specific email types through this transport.
- Default Transport – All site emails will be sent through this transport.
- Mailer Policy – Only the selected email types will be sent through this transport.
- The refresh token renews automatically via cron (interval configurable on the same form); use "Refresh access token now" to renew manually, or re-authorize if Microsoft rejects the stored token.
Similar modules
- Symfony Mailer GraphAPI – Application (client credentials) only.
- Symfony Mailer Microsoft Graph – Application only, built on the official Microsoft Graph PHP SDK.
- Symfony Mailer Lite GraphAPI – Application only, for Symfony Mailer Lite instead of Symfony Mailer.
- Symfony Mailer Lite: Microsoft Graph API / oAuth2 Transport – Application only, for Symfony Mailer Lite.
This module additionally supports Delegated permissions (OAuth2 authorization code + refresh token), sending as a specific, real Microsoft 365 mailbox.
Why choose this module
- Both methods in one module – Application and Delegated are both available, each as its own independent transport, instead of needing separate modules or being limited to one method.
- Run both at once – using Symfony Mailer's transport policy system, different email types can go out through different methods at the same time (e.g. system email via Application, a named mailbox via Delegated).
- No fixed sender mailbox – the sending mailbox is read from the outgoing mail itself, not hardcoded to one address, so it isn't locked to a single fixed mailbox the way some application-only transports are.
- Secrets handled properly – client secrets are resolved through the Key module and never stored in Drupal configuration; refresh tokens and cached access tokens are encrypted at rest.
- Self-maintaining Delegated auth – refresh tokens renew automatically via cron, with manual re-authorize/refresh actions right on the transport's own edit form if Microsoft ever rejects the stored token.
- No separate settings page – everything is configured directly on each transport's native edit form, consistent with how every other Symfony Mailer transport works.