livqenoDocs

ChatInbox

Inbox

List the user's conversations with unread counts and last messages, kept up to date live.

The inbox is the list of conversations a user is in, newest activity first, each with its last message and how many messages they haven't read. It's the left-hand panel of a messaging app, and the number on the app icon.

Where this fits: your backend has added the user as a member of some conversations, and your app is connected. The inbox is read with the user's own chat token. Your backend keeps GET /v1/chat/conversations for admin listings of every conversation.

Load one page

queryMyConversations() returns one page from the server. Use it when you render the list yourself and refresh it on your own schedule:

const { conversations, nextCursor } = await chat.queryMyConversations({ limit: 30 });
 
for (const c of conversations) {
  console.log(c.name, c.unreadCount, c.lastMessage?.content);
}
 
const next = nextCursor ? await chat.queryMyConversations({ cursor: nextCursor }) : null;

Each conversation has:

FieldMeaning
id, name, type, metadataThe conversation. type is DIRECT, CHANNEL or ROOM.
roleThis user's role in it (MEMBER, MODERATOR, ADMIN).
muted, notificationsThis user's notification settings.
lastMessage{ id, seq, senderId, content, type, createdAt }, or null.
unreadCountMessages from others after this user's read position, not counting deleted ones. Stops at 999.
unreadMentionsOf those, how many mention this user.
lastReadSeqThe seq this user has read up to.
updatedAtLast activity. The list is sorted by this, newest first.

Pages hold at most 100 conversations. Only conversations where the user is an active member are listed. Archived conversations are left out, and a token limited to certain conversations only lists those.

A live inbox: chat.inbox

chat.inbox loads the first page and then keeps it current from what the connection receives: a new message moves its conversation to the top and adds to its unread count, and reading clears it. No polling.

await chat.inbox.load({ limit: 30 });
 
const unsubscribe = chat.inbox.subscribe(() => {
  const { conversations, totalUnread, loading, hasMore } = chat.inbox.getSnapshot();
  render(conversations);
  setBadge(totalUnread);
});
 
await chat.inbox.loadMore();  // next page
await chat.inbox.refresh();   // re-read the first page, e.g. when the tab regains focus
chat.inbox.stop();            // stop following live updates

totalUnread adds up the unread counts of every loaded conversation, skipping muted ones. getSnapshot() and subscribe() are shaped for React's useSyncExternalStore.

Marking a message read with markAsRead() updates that conversation's count from the server's answer. A read on another device of the same user also clears it.

Watching: how live updates reach the inbox

The server sends every connection of a member an inbox.updated frame when a message lands in one of their conversations or their read position changes, with no room join needed (see the WebSocket protocol). It isn't sent for live-stream audience chats or for conversations with more than 1,000 members.

Until the SDKs read inbox.updated, load() joins the listed conversations instead:

await chat.inbox.load({ watch: true, watchLimit: 15 }); // the defaults
  • The server allows 20 joined rooms per connection. watchLimit defaults to 15 to leave room for the conversation the user has open.
  • Joining a room shows the user as present in it.
  • Conversations beyond the limit, or with watch: false, update on the next refresh(). The inbox refreshes itself after every reconnect.

A message in a conversation that isn't on the loaded pages (a new DIRECT chat, say) triggers a refresh, so it appears at the top.

Unread counts stay correct

The first page comes from the server with counts already worked out. Live messages are then added on top. A message the server already counted can also arrive live a moment later; the inbox compares its seq with the row's lastMessage.seq and counts it once. Your own messages never count as unread. Sending a message doesn't move your read position on the server: messages from others before yours stay unread until you mark them read.

Audience viewers

A live-stream audience token (chat.isAudienceViewer === true) isn't a member of anything, so it has no inbox. queryMyConversations() rejects with NOT_A_MEMBER without a request.

Common mistakes

MistakeWhat you'll seeFix
Watching more than 20 conversationsTOO_MANY_SUBSCRIPTIONS errors; some rows stop updating.Keep watchLimit under 20, minus the rooms you join yourself.
Counting message events yourself on top of chat.inboxUnread counts double.Use the inbox's counts; it already handles overlap.
Never refreshing in a long-lived tabRows outside watchLimit go stale.Call chat.inbox.refresh() on focus.

Next steps