Conversations & Threads
The inbox is a three-pane layout: a 60px channel rail on the left, a 320px conversation list in the middle, and the open thread + composer on the right.
Conversation key
Every conversation is identified by a key in the form <channel>:<id>:
whatsapp:447700900000messenger:psid_1234567890instagram:igid_abcwechat:openid_xyztelegram:chatid_789viber:viberid_456
The <id> is the parent's id once we've linked the thread to a parent (so all channels for the same parent share that id), or the raw provider id otherwise. The conversation list de-duplicates against this key.
Statuses
A conversation is always in one of four states (chat_conversation_states.status):
| State | What it means | How to get there |
|---|---|---|
| Open | Default — visible in the main view, contributes to unread count | Auto-set on any inbound message |
| Snoozed | Hidden from the main view until snooze_until passes | Snooze button on the thread header |
| Closed | Resolved; archived from the main view | Close button — won't auto-reopen unless a new inbound message arrives |
| Blocked | Sender is on the school blocklist | Use the Block button (admins only) |
If a snoozed conversation passes its deadline, the inbox auto-reopens it on the next poll (or via the app:chat:reopen-snoozed cron for schools nobody's looking at). Reopens fire an audit-log entry with the row count flipped.
Filters
The top filter bar exposes:
- Status — Open / Snoozed / Closed / Blocked (single-select)
- Channel — All / WhatsApp / WeChat / Messenger / Instagram / Telegram / Viber (multi-select)
- Assigned to — Anyone / me / specific staff member (placeholder for upcoming feature)
- Search — full-text search over message body + parent name
The status filter remembers your last choice via chat_inbox_preferences so refreshing doesn't lose it.
Unread badge math
The header pill shows the count of conversations with at least one unread message in your permitted channels — not just total unread messages. A single WhatsApp thread with 15 unread bubbles counts as 1. Reading the thread marks all messages within it as read.
Message order
Each thread renders messages oldest → newest, with date separators (Yesterday, Mon 12 May). Long lists virtualise after 200 rows so very chatty conversations don't slow down the page.
Outbound bubbles you sent show:
- A delivery indicator at the bottom-right of the bubble:
- 🕐 Sending — accepted by Kinderpedia, queued to the platform
- ✓ Sent — platform accepted
- ✓✓ Delivered — the recipient's device acknowledged
- ✓✓ blue Read — the recipient opened it (WhatsApp / Telegram only)
- ⚠️ Failed — see the bubble's error tooltip; the message stays in the thread for you to retry
Failed outbound persists across page refresh so you don't lose context.
Deleting messages
A staff member with chat.manage can soft-delete an individual outbound bubble (it disappears from the thread but is retained for audit purposes). Inbound messages can't be deleted — only the entire conversation can be archived via "Close".