Microsoft Graph (O365) or Resend or SMTP
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.
SMS
Twilio
Teams
Microsoft Teams Bot Framework
Slack
Incoming Webhook
In-App
Stored in PlanetScale, served via API
Architecture
Section titled “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
Section titled “Email Provider”Selecting a provider
Section titled “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
Section titled “Graph email secrets”wrangler secret put EMAIL_PROVIDER # graphwrangler secret put AZURE_TENANT_IDwrangler secret put AZURE_CLIENT_IDwrangler secret put AZURE_CLIENT_SECRETwrangler secret put GRAPH_MAIL_FROM # jd@openinsure.devSee Microsoft 365 Integration for full setup details.
Resend email secrets
Section titled “Resend email secrets”wrangler secret put EMAIL_PROVIDER # resendwrangler secret put RESEND_API_KEY # re_...wrangler secret put RESEND_FROM # noreply@openinsure.devSMS (Twilio)
Section titled “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_SIDwrangler secret put TWILIO_AUTH_TOKENwrangler secret put TWILIO_FROM_NUMBER # +18005551234Microsoft Teams
Section titled “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_PASSWORDSlack notifications use an incoming webhook URL. No OAuth is required.
wrangler secret put SLACK_WEBHOOK_URL # https://hooks.slack.com/services/...In-App Notifications
Section titled “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
Section titled “Dispatch API”Request
Section titled “Request”POST /v1/notifications/dispatchAuthorization: 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
Section titled “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
Section titled “Response”{ "ok": true, "result": { "email": { "success": true, "providerMessageId": "msg_..." }, "teams": { "success": false, "error": "ChatMessage.Send permission not granted" }, "inApp": { "success": true } }}Communications Ledger
Section titled “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=policyThe ledger captures: channel, provider, recipient, subject, status (sent / failed), provider message ID, and trace ID.
Adding a New Template
Section titled “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__/