The inbox is a single view onto every conversation an org has — chat from the website widget, email replies, SMS through the Phone module — surfaced through one set of REST endpoints under /crm/inbox/* and /crm/communications/*. Every message references a contact (or anonymous visitor), every thread carries a status, and every reply can be authored by a staff user, a customer, or an AI assistant.
Conversations
/crm/inbox/conversationsJWT/crm/inbox/conversations/:partiesJWTconversations lists the threads visible to the current user. :parties is a +-joined pair of party identifiers (e.g. cust-abc+staff-xyz) and returns the thread between exactly those two parties — useful for direct-message UIs.
Each conversation carries:
{
id: string;
channel: 'chat' | 'email' | 'sms' | 'whatsapp';
parties: string[]; // contact SK + staff SK
contactId?: string;
subject?: string;
lastMessage: { id: string; text: string; from: string; createdAt: string };
unreadCount: number;
status: 'open' | 'pending' | 'closed' | 'snoozed';
assignedTo?: string;
tags?: string[];
priority?: 'low' | 'normal' | 'high';
updatedAt: string;
}
The channel discriminator drives the rendering — same shape, different message body conventions.
Messages
/crm/inbox/messagesJWT/crm/inbox/messages/:idJWT/crm/chat-message/createJWT/crm/chat-message/updateJWT/crm/chat-message/delete/:idJWT/crm/inbox/messages returns messages within a conversation (filter by conversationId, paginate with before/after). The chat-message/* endpoints write — they share storage with the chat module, so a chat sent through the widget appears in the inbox without extra wiring.
// Send a staff reply
await fetch('/api/crm/chat-message/create', {
method: 'POST',
headers: { 'Content-Type': 'application/json', orgid: ORG_ID, Authorization: `Bearer ${jwt}` },
body: JSON.stringify({
conversationId: convoId,
text: 'Thanks Alice — taking a look now.',
attachments: [],
isInternalNote: false,
}),
});
Set isInternalNote: true and the message is stored on the conversation but not delivered to the customer — used for handoff comments between staff.
Status workflow
/crm/inbox/update-status/:messageId/:statusJWT/crm/inbox/updateJWT/crm/inbox/delete/:idJWT:status accepts open | pending | closed | snoozed. Snooze takes an optional ?until=<iso>; the conversation auto-reopens at that time.
The looser inbox/update endpoint accepts a body to change assignedTo, tags, priority, subject on a conversation:
await fetch('/api/crm/inbox/update', {
method: 'POST',
headers: { 'Content-Type': 'application/json', orgid: ORG_ID, Authorization: `Bearer ${jwt}` },
body: JSON.stringify({
conversationId: convoId,
assignedTo: 'staff-xyz',
priority: 'high',
tags: ['billing', 'refund-request'],
}),
});
Notifications and push tokens
/crm/inbox/notifications/:id?JWT/crm/inbox/save-push-tokenJWTnotifications/:id? returns the staff user's pending in-app notifications. save-push-token registers a mobile device token (FCM / APNs) for push delivery — the inbox app uses this for new-message pushes.
Templates
/crm/send-templateJWTSend a saved message template (subject + body with merge tags) to a contact through the conversation's channel. Templates are stored in the broadcast module and managed via the admin.
await fetch('/api/crm/send-template', {
method: 'POST',
headers: { 'Content-Type': 'application/json', orgid: ORG_ID, Authorization: `Bearer ${jwt}` },
body: JSON.stringify({
templateName: 'order-shipped',
contactId: 'cust-abc',
channel: 'email',
variables: { orderNumber: 'ORD-2026-0042', tracking: '1Z9...784' },
}),
});
Voice and SMS history
The Communications controller is the read-side aggregator over phone and SMS activity:
/crm/communicationsJWT/crm/communications/smsJWT/crm/communications/callsJWT/crm/communications/recordingsJWT/crm/communications/recordings/:recordingSidJWT/crm/communications/statsJWTrecordings/:recordingSid returns the carrier-hosted audio URL plus a transcript if one's been generated. To trigger transcription:
/crm/communications/recordings/transcribeJWTTwilio onboarding
/crm/communications/twilio/setupJWT/crm/communications/twilio/verifyJWT/crm/communications/twilio/available-numbersJWT/crm/communications/twilio/system-phoneJWTThe setup endpoint connects an existing Twilio sub-account to the org; verify confirms the credentials work; available-numbers searches Twilio for purchasable numbers in a given area code; system-phone returns the org's primary outbound number. Number purchase and SMS routing then move to the Phone module.
AI assistant auto-responses
The CRM module ships an ai-assistant sub-controller that wires the inbox to the AI module. When an incoming message arrives on a conversation marked aiAssist: true, the assistant generates a draft reply and either:
- Suggests — saves the draft as a
suggested_replyon the conversation; staff sees it in the UI and clicks Send. - Auto-sends — if the conversation rules permit (low complexity score, customer not VIP-flagged, response is high-confidence), sends without staff review.
Configuration lives at /crm/ai-assistant/* — set the prompt, the channel allow-list, and the auto-send confidence threshold. The assistant uses the same streaming agent surface as the rest of the AI module; auto-replies appear in the conversation as messages with from: 'ai-assistant'.
The AI assistant module is documented separately under the AI section. See the AI overview when it's published; until then, treat the assistant as a configurable hook that consumes conversation messages and produces drafts.
Realtime delivery
The chat / inbox endpoints are REST-only for read and write. Live delivery (typing indicators, "new message" toasts, presence) goes through the WebSocket gateway documented in the Chat module — the inbox UI subscribes to that gateway and updates its REST-fetched view. Both layers reference the same message records, so polling-only clients stay correct, just less snappy.
Events fired
inbox.message_createdinbox.message_received— fired on incoming (customer→staff) onlyinbox.conversation_assignedinbox.conversation_status_changedinbox.template_sent
Wire these into Automation for Slack alerts, SLA timers, or escalation flows.