FluxyChat

Core SDK

Inbox

Cross-room inbox — items feed, counter badges, channels, and realtime onItem.

Inbox model

FluxyChat separates three concepts that are easy to conflate:

ConceptWhat it isSDK / API
Items feedFlat list of inbox rows (mentions, unread rooms, snoozed, follow-ups)useInbox().items
CounterGlobal badge: number of rooms with unread messagesuseInbox().counter
UnseenRows in the current view that need attention (mentions + unread)useInbox().unseen
Channel watermarkPer-room “how far you read” in chat historymarkReadRest(roomId, messageId) / markReadLatest in useChat

The inbox tracks noticing (mentions, unread rooms, snooze, follow-ups).
The room channel tracks reading (last read message id inside a room). Clearing inbox unread does not automatically advance the room watermark unless you call mark-read.

Nest useInbox under FluxyRealtimeProvider (same as useChat). Omit client when the provider owns the session. Member JWT required; guest/pk_ inbox is empty.

Quick start

import { FluxyRealtimeProvider, useInbox } from "@fluxy-chat/react";

export function App({ workerUrl, memberJwt }: { workerUrl: string; memberJwt: string }) {
  return (
    <FluxyRealtimeProvider workerUrl={workerUrl} authTokenProvider={memberJwt}>
      <InboxSidebar />
    </FluxyRealtimeProvider>
  );
}

function InboxSidebar() {
  const { items, counter, unseen, status } = useInbox({ pollIntervalMs: 30_000, realtime: true });

  return (
    <div>
      <h2>Inbox {counter > 0 ? `(${counter})` : ""}</h2>
      {status === "loading" && <p>Loading…</p>}
      <ul>
        {items.map((item) => (
          <li key={item.id}>
            <a href={`/rooms/${item.roomId}`}>
              [{item.kind}] {item.roomName ?? item.roomId}
              {item.unreadCount != null ? ` · ${item.unreadCount} unread` : ""}
            </a>
          </li>
        ))}
      </ul>
    </div>
  );
}

Item kinds

Each row in items is a FluxyInboxItem:

kindSource in REST summaryTypical use
mentionsummary.mentions@you in a room
unreadsummary.unreadRoomsRoom has messages after your watermark
snoozesummary.snoozedRoomsRoom hidden until snoozedUntil
follow_upsummary.followUpsOperator reminder you created
threadsummary.threadsChat reply tree you participate in (threadId is a message id)

Items are built from GET /inbox and merged with realtime pushes on the user channel WebSocket (onItem).

Counter vs unseen

  • countersummary.counts.unreadRooms (or length of unread room list). Use for nav badges.
  • unseen — count of mention + unread rows in the current items feed (respects client query filter).
useInbox({ query: { unread: true } }); // narrows REST snapshot client-side

Mark read (room channel)

Marking a room read advances the channel watermark, not the inbox snooze state:

await client.markReadRest(roomId, messageId);
await reload(); // refresh inbox summary

In chat UI, useChat({ readOn: "mount" }) or markReadLatest: true sends read receipts when the user opens a room.

REST

MethodPathDescription
GET/inboxInbox summary (mentions, unread, snoozed, follow-ups, counts)
POST/inbox/rooms/:roomId/snoozeSnooze a room
POST/inbox/follow-upsSchedule follow-up
POST/rooms/:roomId/readAdvance read watermark { messageId }

Requires authenticated member JWT (same as chat).

Realtime

When realtime: true (default when authenticated), useInbox opens GET /ws/inbox?token= (client.connectInbox()). Handshake is inbox_subscription_succeeded. The same User Durable Object also serves /ws/user/:userId; inbox sockets only receive inbox_updated / inbox_item. Server pushes merge into items and call onItem once per new row.

Console

The dashboard Inbox page (/inbox) shows the items feed, sidebar badge via useInbox().counter, live onItem banner, and Mark read on unread rows.

On this page