Notifications
OpenInsure delivers notifications across five channels from a single dispatch endpoint. All channels are optional — the system gracefully skips any channel whose credentials are absent.
Email
Microsoft Graph (O365) or Resend or SMTP
SMS
Twilio
Teams
Microsoft Teams Bot Framework
Slack
Incoming Webhook
In-App
Stored in PlanetScale, served via API
Architecture
POST /v1/notifications/dispatch
│
├─ email → createEmailProvider() → Graph / Resend / SMTP
├─ sms → createTwilioClient() → Twilio REST API
├─ teams → createTeamsBotClient() → Bot Framework / Graph
├─ slack → createSlackClient() → Incoming Webhook
└─ in_app → db.insert(notifications)
↓
communicationsLedger (delivery audit trail)
The dispatchNotification() function in packages/notify/src/index.ts fans out to all requested channels in parallel and returns a per-channel result. Results are written to communications_ledger for audit.
Email Provider
Selecting a provider
Set EMAIL_PROVIDER in .dev.vars or wrangler.toml [vars]:
| Value | Provider | Best for |
|---|---|---|
graph | Microsoft Graph sendMail | O365 licensed mailboxes, MGA tenants |
resend | Resend API | Transactional email to external recipients |
smtp | SMTP relay | Self-hosted or custom relay |
The Graph provider is the default in production (EMAIL_PROVIDER=graph in wrangler.toml).
Graph email secrets
wrangler secret put EMAIL_PROVIDER # graph
wrangler secret put AZURE_TENANT_ID
wrangler secret put AZURE_CLIENT_ID
wrangler secret put AZURE_CLIENT_SECRET
wrangler secret put GRAPH_MAIL_FROM # jd@openinsure.dev
See Microsoft 365 Integration for full setup details.
Resend email secrets
wrangler secret put EMAIL_PROVIDER # resend
wrangler secret put RESEND_API_KEY # re_...
wrangler secret put RESEND_FROM # noreply@openinsure.dev
SMS (Twilio)
SMS is delivered via Twilio Programmable SMS. The client is created only when both TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN are present.
wrangler secret put TWILIO_ACCOUNT_SID
wrangler secret put TWILIO_AUTH_TOKEN
wrangler secret put TWILIO_FROM_NUMBER # +18005551234
Microsoft Teams
Teams messages are dispatched via the Bot Framework. The Notifications Bot app registration is configured in wrangler.toml:
[vars]
TEAMS_BOT_APP_ID = "ac81bbbd-53fe-4b8a-8755-dafded231e19"
TEAMS_BOT_TENANT_ID = "ebd58a52-c818-4230-b150-348ae1e17975"
wrangler secret put TEAMS_BOT_APP_PASSWORD
The current bot has Mail.Send permission only. ChatMessage.Send must be added and
admin-consented before Teams channel messages work. See Fix Teams
Messaging.
Slack
Slack notifications use an incoming webhook URL. No OAuth is required.
wrangler secret put SLACK_WEBHOOK_URL # https://hooks.slack.com/services/...
In-App Notifications
In-app notifications are stored in PlanetScale (notifications table) and surfaced via the API. The client polls or subscribes via SSE.
Endpoints:
| Method | Path | Description |
|---|---|---|
GET | /v1/notifications?orgId=&read=false | List notifications |
POST | /v1/notifications/:id/read | Mark single read |
POST | /v1/notifications/read-all | Mark all read |
Dispatch API
Request
POST /v1/notifications/dispatch
Authorization: Bearer {token}
Content-Type: application/json
{
"orgId": "00000000-0000-4000-a000-000000000001",
"userId": "10000000-0000-4000-a000-000000000010",
"type": "submission_referred",
"title": "New Submission Referred",
"body": "A new GL submission from Acme Hardware has been referred for UW review.",
"entityType": "submission",
"entityId": "...",
"template": "submission_referred",
"templateData": {
"submissionId": "SUB-2026-00042",
"insuredName": "Acme Hardware",
"line": "General Liability"
},
"channels": ["email", "teams", "in_app"],
"email": { "to": "underwriter@mhcmga.com" },
"teams": { "to": "underwriting@mhcmga.com" }
}
Available templates
| Template | Triggered by |
|---|---|
policy_bound | Policy binds |
claim_opened | FNOL submitted |
claim_settled | Claim closed |
quote_available | UW approves quote |
renewal_reminder | 60/30/15 days before expiry |
invoice_generated | Billing cycle runs |
compliance_deadline | Filing due date approaching |
approval_needed | Authority limit exceeded |
bordereaux_submitted | Bordereaux sent to carrier |
noc_delivery | Notice of cancellation issued |
submission_referred | Submission escalated for review |
endorsement_issued | Mid-term endorsement processed |
cancellation_completed | Policy cancelled |
reinstatement_completed | Policy reinstated |
audit_completed | Premium audit finalized |
nonrenewal_notice | Non-renewal notice issued |
subrogation_update | Subrogation status change |
Response
{
"ok": true,
"result": {
"email": { "success": true, "providerMessageId": "msg_..." },
"teams": { "success": false, "error": "ChatMessage.Send permission not granted" },
"inApp": { "success": true }
}
}
Communications Ledger
Every email and SMS dispatch is written to the communications_ledger table for compliance audit:
GET /v1/notifications/ledger?orgId=&status=failed&entityType=policy
The ledger captures: channel, provider, recipient, subject, status (sent / failed), provider message ID, and trace ID.
Adding a New Template
- Add the template key to the
templateenum inapps/api/src/routes/notifications.ts2. Add the template rendering logic inpackages/notify/src/templates/3. Document the trigger condition in the table above 4. Add tests inpackages/notify/src/__tests__/