# A React notification inbox, without building the backend.

Canonical page: https://notifly.io/use-cases/react-notification-inbox
Last updated: 2026-09-27

A notification center looks like a bell and a list. Behind it sit a feed per user, read and archived state, a live connection, preferences, and the workflows that decide what lands there.

The Notifly Inbox is all of it as one React component on a hosted backend. This page covers what you get, what you can change, and when building your own is the better call. The [in-app inbox docs](https://notifly.io/docs/inbox) cover how the channel works and how to read the feed over the REST API.

## What you get

- **A bell and a feed.** `Inbox` renders a bell with an unread count and a popover feed. Notifications update live over a socket connection, with no polling code in your app.
- **Read and archive states.** Subscribers mark notifications read or unread, **Mark all as read**, archive and unarchive them, and filter by **Unread only** or **Archived**.
- **Tabs.** Split the feed by workflow tags, such as a Comments tab for workflows tagged `comments`.
- **Preferences.** A built-in **Preferences** view lets each subscriber choose which workflows and channels reach them, and workflows check those choices before they send. A workflow you mark as a **Critical workflow**, such as a password reset, can't be turned off there.
- **Actions and links.** Primary and secondary action buttons per notification, with `onNotificationClick` and `routerPush` to hand navigation to your router.
- **Any workflow can write to it.** An in-app step sits in the same workflow as your email, SMS, push and chat steps, and behind a [digest](https://notifly.io/use-cases/digest-notifications) when a burst should arrive as one summary.

## Add it to a React app

```bash
npm install @notiflyio/react@3.17.1
```

Render `Inbox` where the bell should appear. `applicationIdentifier` is the **Application Identifier** on the dashboard's **API Keys** page, `subscriber` is your user's id: the same subscriber id your backend triggers workflows for, and `subscriberHash` is that id signed on your server, as shown below.

```tsx
import { Inbox } from "@notiflyio/react";

export function NotificationCenter({ userId, subscriberHash }: { userId: string; subscriberHash: string }) {
  return (
    <Inbox
      applicationIdentifier="YOUR_APPLICATION_IDENTIFIER"
      subscriber={userId}
      subscriberHash={subscriberHash}
      socketUrl="https://ws.notifly.io"
    />
  );
}
```

`backendUrl` already defaults to `https://api.notifly.io`. Set `socketUrl` to `https://ws.notifly.io`, as above, so the live connection goes to Notifly's websocket service.

## Keep each feed to its own user

The Application Identifier is public, so without a check anyone could open any subscriber's feed. The check is **Security HMAC encryption** on the environment's in-app integration in the Integration Store: with it on, the Inbox only opens a session whose `subscriberHash` matches the subscriber id, signed with your secret key, and refuses one with a missing or wrong hash. New Production environments, and any you add yourself, enforce `subscriberHash` by default. Only Development starts with the switch off, so you can try the Inbox there with just a subscriber id. An environment created before HMAC became the default can still have it off, so check the switch on its in-app integration before going live. Compute the hash on your server and pass it to the component:

```ts
// npm install @notiflyio/api@0.1.26
import { computeSubscriberHash } from "@notiflyio/api";

// On your server, for the signed-in user only: the secret key never reaches the browser.
const secretKey = process.env.NOTIFLY_SECRET_KEY;
if (!secretKey) throw new Error("NOTIFLY_SECRET_KEY is not set");

const subscriberHash = await computeSubscriberHash(secretKey, user.id);
```

## Make it look like your product

Most changes are props. `appearance` takes a base theme, design variables such as colours, font size and radius, and per-element styles. `localization` replaces any string the Inbox shows. `tabs` splits the feed by workflow tags:

```tsx
import { Inbox } from "@notiflyio/react";
import { inboxDarkTheme } from "@notiflyio/react/themes";

export function NotificationCenter({ userId, subscriberHash }: { userId: string; subscriberHash: string }) {
  return (
    <Inbox
      applicationIdentifier="YOUR_APPLICATION_IDENTIFIER"
      subscriber={userId}
      subscriberHash={subscriberHash}
      socketUrl="https://ws.notifly.io"
      appearance={{
        baseTheme: inboxDarkTheme,
        variables: { colorPrimary: "#E5484D", borderRadius: "10px" },
      }}
      localization={{
        "inbox.filters.labels.default": "Activity",
        "notifications.emptyNotice": "You're all caught up.",
      }}
      tabs={[
        { label: "All", filter: { tags: [] } },
        { label: "Comments", filter: { tags: ["comments"] } },
      ]}
    />
  );
}
```

- **Your own notification markup.** `renderNotification` replaces each item with your component, or `renderSubject` and `renderBody` replace parts of it.
- **Your own layout.** `Bell`, `Notifications` and `Preferences` are separate components you can place inside `Inbox` yourself.
- **A fully custom UI.** `NovuProvider` with the `useNotifications` and `useCounts` hooks gives you the data and actions without any of the default interface.

## Build it or use the Inbox?

A notification center is more than its UI. This is what each part takes either way:

| Part | Build it yourself | With the Notifly Inbox |
| --- | --- | --- |
| Storage and API | A notifications table with read, seen and archived state, per-user queries, pagination and an API in front of it. | Hosted: every in-app step writes to the subscriber's feed, served from `api.notifly.io`. |
| Real-time updates | A socket service, reconnection, and a way for your workers to reach it. | The component keeps a socket connection to `ws.notifly.io`; the feed and unread count update live. |
| Interface | Bell, badge, list, empty and loading states, read and archive actions, filters. | `Inbox` renders them; props and render functions change them. |
| Preferences | A settings screen, and the send-time logic that honours it. | The Preferences view, checked before each step of every workflow not marked critical. |
| Other channels | Separate code for email, SMS and push. | The same workflow adds email, SMS, push and chat steps, with digest and delay. |
| Security | Your own authorization on the feed API. | Your server signs the subscriber id; a new environment other than Development refuses a session without the matching hash by default. |
| Where the data lives | In your own database. | On the hosted Notifly platform. |

Build it yourself when notifications must stay in your own database, or when your design is so far from a feed that you would replace the whole interface anyway. Otherwise the Inbox saves the backend, the socket layer and the preference logic, and leaves the look to you.

## Plans and licensing

- **Every plan, Free included.** The Inbox is part of every plan. In-app notifications come from workflows, so each trigger counts as one event, as on any other channel.
- **Branding.** The Inbox shows an "Inbox by Notifly" footer. On Pro and above, turn on **Remove Notifly branding** under Settings, Organization, Branding to hide it. [Compare plans](https://notifly.io/pricing).
- **License.** The @notiflyio/react and @notiflyio/js packages are open source under the MIT license; the Notifly platform they connect to is closed-source and runs as a hosted service.

## Go deeper in the docs

- [In-app inbox](https://notifly.io/docs/inbox): how the in-app channel stores and pushes notifications, and reading the feed over the REST API.
- [Workflows](https://notifly.io/docs/workflows): how the workflows that write to the Inbox are built and triggered.
- [Getting started](https://notifly.io/docs/getting-started): a workspace, an API key and a first triggered workflow.

## Frequently asked questions

### Is there a React component for an in-app notification center?

Yes. The Inbox component from @notiflyio/react renders a bell with an unread count and a live notification feed, with read, archive, tabs and a preferences view built in. You pass it your application identifier, the user's subscriber id and, outside Development, the subscriber hash your server computes for that id.

### Do I need to run a backend for the inbox?

No. The feed and its API are hosted at api.notifly.io and the live connection at ws.notifly.io, which the component reaches with socketUrl set to https://ws.notifly.io. Your backend only triggers workflows and signs each user's subscriber id, which new Production environments require by default.

### How do I stop one user from reading another user's notifications?

Sign each subscriber id on your server. Security HMAC encryption on the environment's in-app integration is on by default in new Production environments and any you add yourself, and off only in Development. An environment created before HMAC became the default can still have it off, so check the switch before going live. With it on, your server computes an HMAC-SHA256 of the subscriber id with your secret key, for example with computeSubscriberHash from @notiflyio/api, and passes it as subscriberHash; a session with a missing or wrong hash is refused.

### Can I change how the inbox looks?

Yes. The appearance prop takes a base theme such as the dark theme, design variables such as colours and radius, and per-element styles; localization replaces any string; and render functions such as renderNotification swap in your own markup. For a completely custom interface, use the hooks instead of the component.

### How do tabs decide which notifications they show?

Each tab filters by the tags on the workflow that sent the notification. Tag your comment workflows comments, give a tab the filter tags comments, and that tab shows only their notifications.

### What license are the inbox packages under?

The @notiflyio/react and @notiflyio/js packages are open source under the MIT license; the Notifly platform they connect to is closed-source and runs as a hosted service.

### Is the inbox included in the free plan?

Yes. The Inbox is on every plan, including Free. Each trigger counts as one event on your plan whatever channels it reaches. The Inbox footer reads Inbox by Notifly, and paid plans from Pro up can remove it.

## More use cases

- Digest notifications: https://notifly.io/use-cases/digest-notifications (markdown: https://notifly.io/use-cases/digest-notifications.md)
