Tenant
ebd58a52-c818-4230-b150-348ae1e17975
palmettoconsulting.us / pcc-mhcis-001
OpenInsure connects to Microsoft 365 through three Azure AD app registrations in the PCC MHC tenant. Together they handle SSO authentication, automated mailbox ingestion, and Teams channel notifications.
Tenant
ebd58a52-c818-4230-b150-348ae1e17975
palmettoconsulting.us / pcc-mhcis-001
Subscription
pcc-mhcis-001
b18f2221-95c0-4eea-936c-0c058e3a8ba1
| App | App ID | Permission | Type | Consent |
|---|---|---|---|---|
| OpenInsure Auth | 790becb2-6ec9-48bd-9a16-3b60861511d7 | User.Read, openid, profile | Delegated | User-level |
| MGA Mailbox Ingest | 6d155a54-3bce-4bde-9302-0c7276c7bea7 | Mail.ReadWrite | Application | ✅ 2026-03-08 |
| Notifications Bot | ac81bbbd-53fe-4b8a-8755-dafded231e19 | Mail.Send | Application | ✅ 2026-03-07 |
The Auth app registration powers Microsoft SSO across all portals. When a user clicks “Sign in with Microsoft”:
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize@mhcmga.com or @openinsure.dev accounthttps://auth.openinsure.dev/callback/microsoftoi-sys-auth) exchanges the code for tokens and issues a portal JWTIaC: infra/azure-auth/main.tf provisions this app registration via Terraform.
Redirect URIs configured:
https://auth.openinsure.dev/callback/microsofthttp://localhost:3000/callback/microsoft (dev)OpenInsure sends transactional email through the Microsoft Graph sendMail endpoint using OAuth2 client credentials (app-only — no user sign-in required).
The queue consumer in packages/notify/src/index.ts checks EMAIL_PROVIDER:
| Value | Provider |
|---|---|
graph | Microsoft Graph (/v1.0/users/{from}/sendMail) |
resend | Resend API |
smtp | SMTP relay |
Set EMAIL_PROVIDER=graph in .dev.vars for local dev or in wrangler.toml [vars] for production.
The Graph provider fetches an OAuth2 client credentials token from:
POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/tokenscope = https://graph.microsoft.com/.defaultgrant_type = client_credentialsTokens are cached in-memory and refreshed 60 seconds before expiry. No external token store is required.
wrangler secret put AZURE_TENANT_ID # ebd58a52-c818-4230-b150-348ae1e17975wrangler secret put AZURE_CLIENT_ID # 6d155a54-3bce-4bde-9302-0c7276c7bea7wrangler secret put AZURE_CLIENT_SECRET # from Azure Portal → app → Certificates & secretswrangler secret put GRAPH_MAIL_FROM # jd@openinsure.dev (licensed O365 mailbox)The sending mailbox (GRAPH_MAIL_FROM) must be a licensed Exchange Online mailbox — shared mailboxes without a license cannot send via Graph.
oi-sys-api polls Underwriting@mhcmga.com every 15 minutes to capture inbound submissions and correspondence.
oi-assets 3. Route —
Each message is handed to the EmailIntakeAgent Durable Object 4. Mark — Messages are marked
read and moved to a processed folderThe cron silently no-ops if GRAPH_SHARED_MAILBOX is not set. This prevents errors in environments where the mailbox integration isn’t configured.
wrangler secret put GRAPH_SHARED_MAILBOX # Underwriting@mhcmga.com# Also uses AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET (same as email)[triggers]crons = ["0 6 * * *", "*/15 * * * *"]# "*/15 * * * *" — mailbox ingest# "0 6 * * *" — daily portfolio sweep| File | Role |
|---|---|
apps/api/src/cron/mailbox-ingest.ts | Cron handler |
packages/notify/src/graph-mailbox.ts | createGraphMailboxReader() |
packages/agents/src/agents/EmailIntakeAgent.ts | DO that processes each message |
An Exchange Online ApplicationAccessPolicy restricts the app’s Mail.ReadWrite permission to Underwriting@mhcmga.com only, preventing access to other tenant mailboxes.
To verify:
Test-ApplicationAccessPolicy -AppId 6d155a54-3bce-4bde-9302-0c7276c7bea7 ` -Identity Underwriting@mhcmga.com# Expected: AccessCheckResult = GrantedThe Notifications Bot sends messages to Microsoft Teams channels via the Bot Framework.
The app ID and tenant are non-secret and committed to wrangler.toml:
[vars]TEAMS_BOT_APP_ID = "ac81bbbd-53fe-4b8a-8755-dafded231e19"TEAMS_BOT_TENANT_ID = "ebd58a52-c818-4230-b150-348ae1e17975"The client secret is injected at deploy time:
wrangler secret put TEAMS_BOT_APP_PASSWORDTeams dispatch is automatically skipped if any of the three variables is absent — the route returns normally with teams: null in the dispatch result.
POST /v1/notifications/dispatch{ "channels": ["teams"], "teams": { "to": "underwriting@mhcmga.com" }, "template": "submission_referred", "templateData": { "submissionId": "...", "risk": "General Liability" }}# 1. Add the permissionaz ad app permission add \ --id ac81bbbd-53fe-4b8a-8755-dafded231e19 \ --api 00000003-0000-0000-c000-000000000000 \ --api-permissions 001f47b3-a01f-4dcd-9bf7-6baf0ac00b88=Role
# 2. Grant admin consentaz ad app permission admin-consent \ --id ac81bbbd-53fe-4b8a-8755-dafded231e19
# 3. Verifyaz ad app show \ --id ac81bbbd-53fe-4b8a-8755-dafded231e19 \ --query "requiredResourceAccess[].resourceAccess[].id"Client secrets expire. The current oi-api-worker-2026 secret expires 2028-03-08.
wrangler secret put AZURE_CLIENT_SECRET (or
TEAMS_BOT_APP_PASSWORD) 3. Record the new expiry in docs/SECRETS_INVENTORY.md 4.
Revoke the old secret in Azure Portal 5. Verify the next cron run in Cloudflare Dashboard
logs (no auth errors)docs/SECRETS_INVENTORY.md — Full secrets inventory with expiry dates and rotation historydocs/archive/TEAMS_NOTIFICATIONS_SETUP.md — Teams Bot initial provisioning walkthroughdocs/archive/AZURE_AD_ADMIN_CONSENT_GUIDE.md — Step-by-step admin consent procedureinfra/azure-auth/main.tf — Terraform for the Auth app registration