Skip to main content

Azure Email Setup Guide

Application: MOC & PCR Management System
Email Provider: Microsoft Graph API (Azure)
Audience: IT Administrator / System Owner


Overview

The MOC-PCR system sends transactional emails (notifications, reminders, approvals) through the application backend.

Emails are delivered using the Microsoft Graph API via a registered Azure application with Mail.Send permission. No external email gateway, third-party SMTP relay, or SES account is required — only your existing Microsoft 365 / Exchange Online environment.

The email system is also deeply integrated with the in-app notification layer. Even if an email delivery attempt fails, the in-app notification is still saved to the database so users can always see the event inside the application.


Architecture Flow

Application Backend (sendEmail)


Acquire OAuth2 token from Microsoft Entra ID
(cached in memory — refreshed automatically before expiry)


POST /v1.0/users/{sender}/sendMail
(Microsoft Graph API)


Email delivered


In-app notification saved to database
(always fires — even if email delivery fails)

Tokens are cached in memory and automatically refreshed two minutes before expiry, so no repeated round-trips to Microsoft are made per email.


Prerequisites

RequirementDetails
Azure SubscriptionActive subscription with an admin account
Microsoft Entra IDTenant already provisioned (same tenant used for SSO is fine)
Licensed Exchange mailboxThe sender mailbox must have an Exchange Online / M365 licence
Application serverMust be able to reach login.microsoftonline.com and graph.microsoft.com

Important: The sender account must have an active Exchange Online mailbox. A shared mailbox or a licensed M365 user both work. A plain Entra ID identity with no Exchange licence cannot send mail via Graph.


Part 1 — Azure Portal Configuration

Step 1 — Register (or Reuse) the Application

If you already completed the SSO setup you already have an app registration. You can reuse the same registration — just add the permissions below to it. Skip to Step 2 if so.

Otherwise:

  1. Sign in to the Azure Portal.
  2. Search for and open Microsoft Entra ID.
  3. Click App registrationsNew registration.
  4. Fill in:
    • Name: MOC-PCR System (or your existing name)
    • Supported account types: Accounts in this organizational directory only
    • Redirect URI: leave blank (not needed for this flow)
  5. Click Register.

Copy and save the Application (client) ID and Directory (tenant) ID from the overview page.


Step 2 — Add Mail.Send Permission

  1. Inside the app registration, go to API permissionsAdd a permission.
  2. Select Microsoft GraphApplication permissions.
  3. Search for and select Mail.Send.
  4. Click Add permissions.
  5. Click Grant admin consent for [Your Organization]Yes.

Mail.Send must show a green ✓ Granted status before continuing.

Application permissions vs Delegated permissions:
This flow uses application permissions (client credentials grant, no user interaction). Do not add Mail.Send as a Delegated permission — that requires a signed-in user and will not work from a server process.


Step 3 — Create (or Reuse) a Client Secret

If you already have a client secret from the SSO setup, you can reuse it.

Otherwise:

  1. Go to Certificates & secretsClient secretsNew client secret.
  2. Set a description (e.g., MOC-PCR Mail Secret) and an expiry (12 or 24 months recommended).
  3. Click Add.
  4. Immediately copy the Value column — it is shown only once.

⚠️ Azure shows two columns after creating a secret:

  • Secret ID — a GUID for Azure Portal management only. Do not use this.
  • Value — the actual secret (e.g., mXK8Q~...). This goes in your environment variables.

Step 4 — Identify the Sender Mailbox

The Graph API sends mail on behalf of a specific user (or shared mailbox). This account must:

  • Have an active Exchange Online / Microsoft 365 licence, or be a shared mailbox.
  • Be in the same tenant as the registered application.

Note the full email address of this account (e.g., no-reply@yourcompany.com). This becomes the GRAPH_MAIL_SENDER environment variable.


Part 2 — Application Environment Variables

Set the following variables on the server where the application is deployed. These must never be committed to source control.

# ── Email provider (must be set to "graph") ─────────────────────────────────
EMAIL_PROVIDER=graph

# ── Microsoft Graph Mail ────────────────────────────────────────────────────
AZURE_TENANT_ID=<Directory (tenant) ID>
AZURE_CLIENT_ID=<Application (client) ID>
AZURE_CLIENT_SECRET=<Secret VALUE — NOT the Secret ID>
GRAPH_MAIL_SENDER=<sender email address, e.g. no-reply@yourcompany.com>

AZURE_TENANT_ID, AZURE_CLIENT_ID, and AZURE_CLIENT_SECRET are the same values already configured for SSO. You do not need a separate app registration.

Verification Checklist

VariableExpected FormatWhere to Find
EMAIL_PROVIDERMust be the literal string graphSet manually
AZURE_TENANT_IDUUID (e.g. 10091248-3181-...)App registration → Directory (tenant) ID
AZURE_CLIENT_IDUUID (e.g. 3dd121a7-9c06-...)App registration → Application (client) ID
AZURE_CLIENT_SECRETAlphanumeric string with special chars (e.g. mXK8Q~8vJ...)Certificates & secrets → Value column only
GRAPH_MAIL_SENDERFull email address (e.g. no-reply@yourcompany.com)The licensed mailbox chosen in Step 4

Part 3 — Token Caching

The application caches the OAuth2 access token in memory:

  • Tokens are valid for 60 minutes (standard Microsoft token lifetime).
  • The application refreshes the token 2 minutes before expiry to ensure no expired tokens are ever used.
  • On a cold start (fresh server boot) the first email triggers a token fetch. All subsequent emails within 58 minutes reuse the cached token with no extra network call.
  • The cache lives in the Node.js server process. In a multi-process environment (e.g., PM2 cluster mode or multiple Docker replicas), each process maintains its own token — this is safe and correct.

Part 4 — Secret Rotation

Client secrets expire. Before the secret reaches its expiry date:

  1. Go to Azure Portal → App registrations → your app → Certificates & secrets.
  2. Create a new client secret.
  3. Update AZURE_CLIENT_SECRET in your environment variables with the new Value.
  4. Restart the application server.
  5. Confirm emails are being sent, then delete the old secret from Azure Portal.

Recommended: Set a calendar reminder 30 days before the secret's expiry date.


Part 5 — Email Send Flow (Technical Reference)

sendEmail(payload)


getGraphToken()

├─ cache hit? ──▶ return cached token

└─ cache miss? ──▶ POST /oauth2/v2.0/token (client_credentials)


Cache token (expires_in - 120s)


POST /v1.0/users/{sender}/sendMail
Authorization: Bearer {token}

├─ success ──▶ { success: true }

└─ failure ──▶ error logged — execution continues below


sendNotification() — ALWAYS fires (success or failure)
Persists in-app notification to database

Troubleshooting

Error messageLikely causeResolution
Graph token request failed: ...Wrong AZURE_TENANT_ID, AZURE_CLIENT_ID, or AZURE_CLIENT_SECRETVerify all three values; ensure the secret has not expired
GRAPH_MAIL_SENDER env variable is missingGRAPH_MAIL_SENDER not setAdd the full sender email address to environment variables
Graph email failed: 403 ...Mail.Send permission not granted, or admin consent not givenGo to API permissions → Grant admin consent
Graph email failed: 404 ...Sender mailbox does not exist or has no Exchange licenceConfirm the sender account exists and has an active M365 / Exchange Online licence
Graph email failed: 401 ...Token invalid or expired mid-requestShould auto-recover on next call; check that system clock is accurate

Next Steps

With Microsoft Graph email configured and all environment variables noted, you now have everything needed for deployment. Proceed to:

Docker Deployment →


Last updated: March 2026