FluxyChat

Chat Features

Inbox and notifications

Combine inbox feeds, read state, user-channel events, and web push.

FluxyChat splits inbox (cross-room attention) from notifications (delivery channels). This guide shows how to wire both in a support or team app.

Mental model

LayerPurposePrimary API
Inbox itemsMentions, unread rooms, snooze, follow-upsuseInbox()
Room watermarkHow far you read inside one roommarkReadRest / useChat().markReadLatest
User channelRealtime inbox refresh, system alertsuseNotifications() / useUserChannel()
Web pushBrowser notifications when tab is closeduseWebPush()

Clearing inbox unread does not automatically advance the room watermark — call mark-read explicitly when the user opens a thread.

Inbox sidebar

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

function InboxNav() {
  const { items, counter, unseen, onItem, reload } = useInbox({
    pollIntervalMs: 30_000,
    realtime: true,
    onItem: (item) => {
      toast(`New ${item.kind} in ${item.roomName ?? item.roomId}`);
      reload();
    },
  });

  return (
    <nav>
      <span>Inbox {counter > 0 ? `(${counter})` : ""}</span>
      <ul>
        {items.map((item) => (
          <li key={item.id}>
            <a href={`/rooms/${item.roomId}`}>
              [{item.kind}] {item.roomName ?? item.roomId}
            </a>
          </li>
        ))}
      </ul>
      {unseen > 0 && <Badge>{unseen} unseen</Badge>}
    </nav>
  );
}

See Inbox model for item kinds and counter vs unseen.

Mark read when opening a room

import { useChat } from "@fluxy-chat/react";

function RoomThread({ roomId }: { roomId: string }) {
  const { messages, markReadLatest } = useChat(roomId);

  useEffect(() => {
    if (messages.length > 0) markReadLatest();
  }, [messages.length, markReadLatest]);

  return <MessageList messages={messages} />;
}

After mark-read, refresh inbox summary:

await client.markReadRest(roomId, latestMessageId);
await reloadInbox();

User channel events

Subscribe to per-user realtime events (inbox pushes, mentions, system alerts):

import { useNotifications } from "@fluxy-chat/react";

useNotifications({
  client,
  onEvent: (event) => {
    if (event.type === "inbox_item") reloadInbox();
    if (event.type === "mention") playSound();
  },
});

Member JWT is required — the user channel is scoped to userId in the token.

Web push

import { useWebPush } from "@fluxy-chat/react";

function PushToggle({ client }: { client: FluxyChatClient }) {
  const { permission, requestPermissionAndSubscribe } = useWebPush(client);

  if (permission === "unsupported") return null;

  return (
    <button onClick={() => requestPermissionAndSubscribe()}>
      {permission === "granted" ? "Push on" : "Enable notifications"}
    </button>
  );
}

Configure VAPID keys on the Worker:

VAPID_PUBLIC_KEY=...
VAPID_PRIVATE_KEY=...

See Web push (VAPID).

Delivery matrix

ChannelWhen it firesRequires
In-app badgeInbox counter / unseenActive session or poll
User channel WSRealtime inbox itemMember JWT + WS
Web pushMention / DM / configured rulesVAPID + browser permission
Email / SMSTenant integrationsWorker config

Dashboard parity

The hosted console implements this stack at /inbox and /notifications. Use it as a reference implementation when building your own operator UI.

See also

On this page