# 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

```tsx title="components/header.tsx"
import { NotificationBell } from '@buildbase/sdk/react';

export function Header() {
  return (
    <header className="flex items-center justify-between">
      
      
    </header>
  );
}
```

```tsx title="app/notifications/page.tsx"
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()`](/notifications/overview), 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

| State    | Set when                                                                    |
| -------- | --------------------------------------------------------------------------- |
| Seen     | The inbox was opened while the item was in it. Clears the bell's highlight. |
| Read     | The item was opened in the inbox, or its email or push link was clicked.    |
| Archived | The 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:

| Value          | Items                                                        |
| -------------- | ------------------------------------------------------------ |
| omitted        | Everything the user has been sent                            |
| `'current'`    | The selected workspace's. Waits while none is selected       |
| a workspace id | That workspace's                                             |
| `'none'`       | Account notices that belong to no workspace (sign-in alerts) |

## Your own UI

```tsx title="components/notification-list.tsx"
import { useNotifications } from '@buildbase/sdk/react';

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

  if (loading) return ;

  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>
  );
}
```

| Returned        | Type                    | Description                                        |
| --------------- | ----------------------- | -------------------------------------------------- |
| `notifications` | `INotificationItem[]`   | Loaded items, newest first, archived ones left out |
| `unread`        | `INotificationItem[]`   | The loaded items not yet read                      |
| `counts`        | `{ unread, unseen }`    | Server counts for the whole scope, not only loaded |
| `loading`       | `boolean`               | True while the first page loads                    |
| `hasMore`       | `boolean`               | Another page is available                          |
| `error`         | `string \| null`        | The 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                         |

| Option           | Type      | Default | Description                                        |
| ---------------- | --------- | ------- | -------------------------------------------------- |
| `workspaceId`    | `string`  | omitted | See [Scope](#scope)                                |
| `pageSize`       | `number`  | `20`    | Items per page, at most 100                        |
| `pollIntervalMs` | `number`  | `60000` | How often to check for new items. `0` turns it off |
| `live`           | `boolean` | `true`  | Hear 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

- [Send a notification](/notifications/overview)
- [Push notifications](/push-notifications/overview)
- Full API: [API Reference](/reference/api-reference)
