Communications
Outbound customer and staff messaging — segmented WhatsApp/email reminders, two-way WhatsApp inbox, and staff push notifications.
Scope
This module covers external comms to customers plus internal push blasts. One-to-one internal chat lives under /internal/chat (see docs/features/internal.md).
Pages
src/pages/communications/Communications.tsx— reminder composer + scheduled queue at/communications.src/pages/communications/WhatsAppInbox.tsx— live two-way WhatsApp conversation list at/whatsapp.
Route guards (src/App.tsx:179-180):
<Route path="/communications" element={<ProtectedRoute requiredPermissions={['customers.view']}><Communications /></ProtectedRoute>} />
<Route path="/whatsapp" element={<ProtectedRoute requiredPermissions={['customers.view']}><WhatsAppInbox /></ProtectedRoute>} />
Permission drift
Both routes use customers.view today. Tracked as drift item #4 in docs/PERMISSIONS.md §8 — should migrate to a dedicated communications.view.
Channels supported
| Channel | Surface | Delivery backend |
|---|---|---|
| WhatsApp (outbound) | Reminder composer, WhatsApp Inbox | WhatsApp Cloud API, configured via Admin → Integrations. getWhatsAppConfig() must return a valid config or the inbox shows a config error. |
| WhatsApp (inbound) | WhatsApp Inbox | Inbound webhooks into WhatsAppMessage + WhatsAppContact tables. Realtime subscription via Supabase. |
| Reminder composer | Resend provider (configured under Admin → Integrations) | |
| Push | Staff Push card (StaffPushCard) |
Supabase Edge Function push-send. Targets only devices with push enabled under Settings → Security. |
| Call / SMS / in-app | Log Contact dialog on Approvals, Requests, WhatsApp |
Manual log only — no actual SMS send wiring exists today. |
Surface: Communications (/communications)
Three sub-cards on the same page (Communications.tsx:554 onwards):
Reminder Composer
Target customers by segment and channel:
| Segment | Meaning |
|---|---|
| All Active | Any customer with ≥1 active (non-cancelled, non-rejected) booking |
| Payment Due | Active customers with balanceAmount > 0 |
| Visa Pending | Customers whose visa case is not approved/rejected/collected/completed |
| Departure in 7 Days | Customers whose group departs in the next 7 days |
Quick templates map to segments: Payment Due, Passport & Documents, Travel Readiness.
Delivery modes: Send now or Schedule (uses DateTimeInput).
All sends go through POST /operations/communications (batched via Promise.allSettled).
100-recipient cap
The client refuses to send to more than 100 recipients in one batch.
Staff Push Notification (StaffPushCard)
- Target: all staff, by role, or a specific user.
- Title (≤80 chars), body (≤200 chars), optional click-through URL.
- Invokes Edge Function
push-sendwith a bearer token from the current Supabase session. - Counts successes ("Sent to N of M subscribed devices") — non-subscribed devices are silently skipped.
Scheduled Queue
Read-only list of up to 12 pending items from GET /communications/queue?status=pending&limit=12. Updated optimistically when Schedule is used.
Surface: WhatsApp Inbox (/whatsapp)
Full two-way chat window (WhatsAppInbox.tsx):
- Contact list with unread + last-message preview.
- Conversation pane with per-message status icons (sent → delivered → read → failed).
- Quick templates (
QUICK_TEMPLATESfromsrc/types/whatsapp.ts). - Realtime —
subscribeToMessages()+subscribeToContacts()keep the list and open thread live. - New chat dialog uses
PhoneInputto look up or create a contact withfindOrCreateContact().
Contact resolution
A WhatsApp contact can be linked to a Customer, an Agent, or stand alone as a bare phone number. Display name falls back through displayName → customer → agent → profileName → +phone (getContactDisplayName in WhatsAppInbox.tsx:65).
API endpoints
| Method | Path | Purpose |
|---|---|---|
| GET | /operations/communications |
Log list (filters: entityType, entityId, limit) |
| POST | /operations/communications |
Create a log entry; triggers actual delivery if channel is WhatsApp/email and sendNow !== false |
| GET | /communications/queue?status=pending |
Scheduled queue |
| Edge fn | push-send |
Staff push delivery |
| Supabase RPC / table ops | whatsapp_contacts, whatsapp_messages |
Two-way WhatsApp |
Permissions
| Action | Permission | Enforcement |
|---|---|---|
View /communications or /whatsapp |
customers.view |
Route guard (drift) |
| Send communication | admin.users.edit |
Server gate in handleOperationsCommunications, src/lib/api.ts:13201 (drift — should be communications.create) |
| Send staff push | Authenticated staff session | Edge function validates bearer; no matrix permission today |
| View scheduled queue | customers.view |
Inherits route |
Related
- Admin → Integrations (Resend + WhatsApp Cloud API setup) →
docs/features/admin.md. - Push opt-in / opt-out per device →
docs/features/settings.md. - Internal staff chat →
docs/features/internal.md.