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
| Requirement | Details |
|---|---|
| Azure Subscription | Active subscription with an admin account |
| Microsoft Entra ID | Tenant already provisioned (same tenant used for SSO is fine) |
| Licensed Exchange mailbox | The sender mailbox must have an Exchange Online / M365 licence |
| Application server | Must 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:
- Sign in to the Azure Portal.
- Search for and open Microsoft Entra ID.
- Click App registrations → New registration.
- 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)
- Name:
- Click Register.
Copy and save the Application (client) ID and Directory (tenant) ID from the overview page.
Step 2 — Add Mail.Send Permission
- Inside the app registration, go to API permissions → Add a permission.
- Select Microsoft Graph → Application permissions.
- Search for and select Mail.Send.
- Click Add permissions.
- Click Grant admin consent for [Your Organization] → Yes.
Mail.Sendmust 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 addMail.Sendas 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:
- Go to Certificates & secrets → Client secrets → New client secret.
- Set a description (e.g.,
MOC-PCR Mail Secret) and an expiry (12 or 24 months recommended). - Click Add.
- Immediately copy the
Valuecolumn — 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, andAZURE_CLIENT_SECRETare the same values already configured for SSO. You do not need a separate app registration.
Verification Checklist
| Variable | Expected Format | Where to Find |
|---|---|---|
EMAIL_PROVIDER | Must be the literal string graph | Set manually |
AZURE_TENANT_ID | UUID (e.g. 10091248-3181-...) | App registration → Directory (tenant) ID |
AZURE_CLIENT_ID | UUID (e.g. 3dd121a7-9c06-...) | App registration → Application (client) ID |
AZURE_CLIENT_SECRET | Alphanumeric string with special chars (e.g. mXK8Q~8vJ...) | Certificates & secrets → Value column only |
GRAPH_MAIL_SENDER | Full 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:
- Go to Azure Portal → App registrations → your app → Certificates & secrets.
- Create a new client secret.
- Update
AZURE_CLIENT_SECRETin your environment variables with the new Value. - Restart the application server.
- 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 message | Likely cause | Resolution |
|---|---|---|
Graph token request failed: ... | Wrong AZURE_TENANT_ID, AZURE_CLIENT_ID, or AZURE_CLIENT_SECRET | Verify all three values; ensure the secret has not expired |
GRAPH_MAIL_SENDER env variable is missing | GRAPH_MAIL_SENDER not set | Add the full sender email address to environment variables |
Graph email failed: 403 ... | Mail.Send permission not granted, or admin consent not given | Go to API permissions → Grant admin consent |
Graph email failed: 404 ... | Sender mailbox does not exist or has no Exchange licence | Confirm the sender account exists and has an active M365 / Exchange Online licence |
Graph email failed: 401 ... | Token invalid or expired mid-request | Should 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:
Last updated: March 2026