BuildBaseBuildBase

Inbox

Give every user a notification inbox they can come back to, with a bell, read state and archive.

Everything your app sends a user, by email, push or both, is kept for them as an inbox. One notification is one item however it was delivered, so an event sent both ways shows once, and the item says what each channel did.

Note

Requires @buildbase/sdk 0.0.71 or later, and a BuildBase server from release 22 . Live updates need the same; on an older server the socket is refused and the one-minute poll carries on.

Quick example

import { NotificationBell } from '@buildbase/sdk/react';

export function Header() {
  return (
    <header className="flex items-center justify-between">
      <Logo />
      <NotificationBell />
    </header>
  );
}
import { NotificationInbox } from '@buildbase/sdk/react';

export default function NotificationsPage() {
  return <NotificationInbox workspaceId="current" />;
}

The bell shows the unread count and opens the inbox in a panel. The page shows the same list in full. Both read one shared list, so reading an item in either updates the other at once.

What goes in the inbox

Every notification sent to a user: the built-in events (invitations, billing, credits, sign-in alerts), your own events sent with bb.notification.send(), workflow emails and pushes, and push campaigns.

Sign-in codes, magic links and password resets are never in it. A stored, readable login code is a credential; those stay in the console's email log only.

A user sees their own notifications and nobody else's. The whole workspace's history, every member's, is in the BuildBase console under Notifications → Delivery log, and on each workspace and user page.

Read, seen and archived

StateSet when
SeenThe inbox was opened while the item was in it. Clears the bell's highlight.
ReadThe item was opened in the inbox, or its email or push link was clicked.
ArchivedThe user archived it. It leaves the list and stays in their history.

An email open never marks an item read: some mail clients load the tracking pixel before anyone reads the message. It is recorded on the item as "Opened", and a click is recorded as "Clicked".

When the user clicks an item, it is marked read and its link opens. A link to another site goes through the same link check as a push click. A path inside your app (/billing) opens as it is.

Scope

workspaceId chooses which items, on the hook and on both components:

ValueItems
omittedEverything the user has been sent
'current'The selected workspace's. Waits while none is selected
a workspace idThat workspace's
'none'Account notices that belong to no workspace (sign-in alerts)

Your own UI

import { useNotifications } from '@buildbase/sdk/react';

export function NotificationList() {
  const { notifications, counts, loading, markRead, markAllRead } =
    useNotifications({ workspaceId: 'current' });

  if (loading) return <Spinner />;

  return (
    <section>
      <button onClick={markAllRead} disabled={counts.unread === 0}>
        Mark all read ({counts.unread})
      </button>
      {notifications.map((item) => (
        <button key={item.id} onClick={() => markRead(item.id)}>
          {item.readAt ? item.title : <strong>{item.title}</strong>}
        </button>
      ))}
    </section>
  );
}
ReturnedTypeDescription
notificationsINotificationItem[]Loaded items, newest first, archived ones left out
unreadINotificationItem[]The loaded items not yet read
counts{ unread, unseen }Server counts for the whole scope, not only loaded
loadingbooleanTrue while the first page loads
hasMorebooleanAnother page is available
errorstring | nullThe last load or action error
loadMore() => Promise<void>Append the next page
markRead(id) => Promise<void>Read in the inbox
markUnread(id) => Promise<void>Back to unread
archive(id) => Promise<void>Out of the list, kept in history
markAllRead() => Promise<void>Read everything in scope
markSeen() => Promise<void>Clear the "new" highlight without reading anything
refresh() => Promise<void>Fetch the first page again
OptionTypeDefaultDescription
workspaceIdstringomittedSee Scope
pageSizenumber20Items per page, at most 100
pollIntervalMsnumber60000How often to check for new items. 0 turns it off
livebooleantrueHear about changes the moment they happen

Edge cases

  • Signed out: the list is empty and nothing loads.
  • New items: arrive live, over one connection per tab shared by every bell and inbox on the page. The connection carries no content, only "your inbox changed", and the list refetches. The count is still polled every minute as a fallback, and straight away when the tab comes back into view.
  • History window: the inbox shows as far back as your plan shows email history.
  • Sending: bb.notification.send() from the SDK is limited to 300 an hour per workspace, because each send lands in every recipient's inbox.
  • A failed action: the change is undone on screen and error is set.

Next steps