Skip to content

Microsoft 365 Endpoints

The M365 transport sends through the Microsoft Graph sendMail API using an Entra ID app registration with application permissions (client-credentials flow — no user login, right for a service).

One-time Entra ID setup

  1. Create an app registration: Entra admin center → Identity → Applications → App registrations → New registration. Name it (e.g. pg-relay-notifier), single tenant, no redirect URI.
  2. Add the permission: API permissions → Add a permission → Microsoft Graph → Application permissionsMail.Send.
  3. Grant admin consent for the tenant (button on the API permissions page — the Status column must show a green "Granted"; consent can take several minutes to propagate).
  4. Create a client secret: Certificates & secrets → New client secret. Copy the secret value immediately — it is shown once. Note its expiry and calendar the rotation.
  5. Record the Directory (tenant) ID and Application (client) ID from the Overview page.
  6. Recommended — limit the blast radius: by default Mail.Send lets the app send as any mailbox in the tenant. Scope it to just the sending mailbox with an application access policy (Exchange Online PowerShell):
New-ApplicationAccessPolicy -AppId <client-id> -PolicyScopeGroupId sender-group@example.com `
    -AccessRight RestrictAccess -Description "pg_relay_notifier may send only as members of this group"

The profile

SELECT pgrelay_notifier.create_profile(
    p_profile_name => 'm365',
    p_transport    => 'm365',
    p_profile      => '{
        "tenant_id": "00000000-0000-0000-0000-000000000000",
        "client_id": "11111111-1111-1111-1111-111111111111",
        "client_secret": "_env:M365_CLIENT_SECRET",
        "sender":    "notifications@example.com",
        "save_to_sent_items": false
    }'::jsonb,
    p_channel      => 'notifications'
);
Key Required Meaning
tenant_id Entra ID tenant
client_id App registration id
client_secret The app registration's client secret — must be an "_env:VAR_NAME" reference, never a literal value
sender Sending mailbox (userPrincipalName or object id)
save_to_sent_items Default false
timeout_seconds Default 30, cap 120 (covers token fetch + send)

client_secret is required for this transport, exactly as password is for authenticated SMTP — it names the Processor-host environment variable holding the app registration's client secret via the same "_env:VAR_NAME" convention (see SMTP Endpoints for the full rationale). Tokens are acquired and cached by the Processor automatically.

Things worth knowing

  • With both body_text and body_html set on a notification, Graph carries a single body and HTML wins.
  • Attachments are capped at 3 MB total per notification by default — see Attachment Limits and Profile Defaults.
  • The Graph request-id of an accepted send is recorded as the notification's provider_ref — quote it to Microsoft support when tracing a message.
  • A 403 ErrorAccessDenied on sends usually means admin consent was never actually granted (or is still propagating), the permission was added as Delegated instead of Application, or an application access policy blocks the sender mailbox.