Prebuilt Inbox Component
This guide covers the prebuilt Notification Inbox components for an Indie Notification Inbox.
native-notify ships a prebuilt Notification Inbox: a bell icon for your app header with an unread dot, and a full-screen inbox screen that opens when the bell is tapped. In mode="indie" the inbox displays the current user's notifications — the same data returned by the getIndieNotificationInbox function.
A Pro plan feature:
The notification inbox is included in the Free, Pro and Team plans. On Premium, every inbox endpoint answers 201 with an explanation instead of notifications ("Your current Native Notify membership cannot access notification inbox features…"), so the SDK shows an empty inbox and an unread count of 0. Notifications you send keep being recorded, so they appear once the account upgrades. See what each plan includes.
Quick start
Add the <NotificationInboxBell> component to your header with mode="indie" and the user's subId:
import { NotificationInboxBell } from 'native-notify';
// Expo Router / React Navigation header:
<Stack.Screen
name="index"
options={{
headerRight: () => (
<NotificationInboxBell
appId={yourAppId}
appToken="yourAppToken"
mode="indie" // or "mass" (default)
subId={currentUser.id} // required when mode="indie"
/>
),
}}
/>
The subId is the unique user ID you registered with Native Notify during the Indie Push Notification registration process — the component uses it to fetch that user's inbox.
appId and appToken are optional on every inbox component when you set them once with NativeNotify.init() or <NativeNotifyProvider> — pass them explicitly only if you prefer.
Indie mode notes
- Per-row delete is available. Each row shows a delete button that removes only that user's notification using the deleteIndieNotificationInbox function. The row is removed optimistically and is restored if the delete request fails.
- Hide the delete button by passing
allowDelete={false}. - Works in Expo Go, on simulators, and on web. Read state is tracked per user (
subId), so the unread dot works everywhere — unlike mass mode, which needs a real device for its push token. - Opening the inbox marks every notification of that
subIdas read (not just the page it fetched), so the unread dot clears when the inbox opens — unless you opt intoperNotificationReadbelow. - The app icon badge follows the unread count — whenever the unread count refreshes, it is synced to the app icon badge with
Notifications.setBadgeCountAsync(). PasssyncBadge={false}to opt out.
Per-notification read state
Requires native-notify v5.2+.
By default, opening the inbox marks the whole inbox as read on the server and the unread dot clears. If you want each row to keep its own read state instead — an unread dot per row, marked read as each row is opened — pass perNotificationRead:
<NotificationInboxBell
appId={yourAppId}
appToken={appToken}
mode="indie"
subId={currentUser.id}
perNotificationRead
/>
With the option on:
- Pages are fetched with each row's real
readflag, and the server no longer marks the whole inbox read on fetch. - Unread rows show a dot, and tapping a row marks it read and refreshes the unread count.
- The unread count comes from the server instead of being zeroed when the inbox opens, so the bell follows the rows that are still unread.
- Read state is tracked per subscriber (
subId), so — unlike mass mode — it works on simulators, in Expo Go, and on web.
The legacy behavior stays the default — existing apps keep their exact same requests.
Props
NotificationInboxBell accepts the following props:
| Prop | Type | Default | Description |
|---|---|---|---|
appId | number | string | — | Your Native Notify App ID. |
appToken | string | — | Your Native Notify App Token. |
mode | 'mass' | 'indie' | 'mass' | Set to 'indie' for per-user notifications. |
subId | number | string | — | Required when mode="indie" (the user's unique ID). |
take | number | 20 | Number of notifications fetched per page. |
syncBadge | boolean | true | Sync the unread count to the app icon badge. |
perNotificationRead | boolean | false | Per-row unread dots + mark-on-open, instead of marking the whole inbox read on fetch — see Per-notification read state. |
colors | object | light/dark theme | Partial theme override — see Colors. |
title | string | 'Notifications' | Inbox header title. |
emptyText | string | "You're all caught up" | Empty-state body text. |
allowDelete | boolean | true | Show the per-row delete button (indie mode only). Pass false to hide it. |
onNotificationPress | (notification) => void | — | Called when a row is tapped. |
onOpen | () => void | — | If set, called instead of opening the built-in inbox screen. |
showCount | boolean | false | Show a numeric unread badge instead of a plain dot. |
maxCount | number | 99 | Badge cap — shows "99+" past it. |
renderIcon | ({ unreadCount, color }) => node | bundled bell icon | Custom bell icon. |
iconSize | number | 24 | Bell icon size. |
iconStyle | StyleProp<ImageStyle> | — | Style override for the bell icon. |
containerStyle | StyleProp<ViewStyle> | — | Style override for the bell container. |
Colors
Colors follow the device's light/dark mode automatically. Pass any subset of the keys below in colors to override the theme:
<NotificationInboxBell
appId={yourAppId}
appToken="yourAppToken"
mode="indie"
subId={currentUser.id}
colors={{ dot: '#EF4444', accent: '#2563EB' }}
/>
icon,dot,badgeTextbackground,headerBackground,card,bordertitle,text,mutedText,emptyTitle,emptyTextaccent,delete
Custom screen and headless hook
Want your own trigger instead of the bell? Render <NotificationInboxScreen> and control the modal yourself with visible / onClose — it accepts the props above except the bell-only ones (onOpen, showCount, maxCount, renderIcon, iconSize, iconStyle, containerStyle), plus these:
| Prop | Type | Default | Description |
|---|---|---|---|
visible | boolean | — | Whether the inbox modal is shown. |
onClose | () => void | — | Called when the modal is dismissed. |
To build a fully custom UI, use the headless hook:
import { useNotificationInbox } from 'native-notify';
const {
notifications, // fetched inbox rows
unreadCount, // unread count for your own badge
loading, // initial load in progress
refreshing, // pull-to-refresh in progress
loadingMore, // next page in progress
hasMore, // more pages available
error, // error message, if any
openInbox, // loads page 1 — and marks the whole inbox read, unless perNotificationRead
refresh,
refreshUnread,
loadMore,
deleteNotification, // per-row delete (indie mode)
perNotificationRead, // true when you built the hook with the option
markNotificationRead, // mark ONE row read when it is opened (per-notification mode)
} = useNotificationInbox({
appId: yourAppId,
appToken: 'yourAppToken',
mode: 'indie',
subId: currentUser.id, // required when mode: 'indie'
take: 20,
syncBadge: true, // sync the unread count to the app icon badge
});
The hook fetches only the unread count on mount — call openInbox() (or refresh()) to load the rows. markNotificationRead takes the row object (not an id).
Exact pagination is available for custom UIs: getIndieNotificationInboxPage() returns { rows, total } — the server's X-Total-Count — so "load more" is exact instead of a guess.
Inbox row data
Each row in the inbox is shaped like this:
{
notification_id, // unique notification ID — used for deletes
date, // notification date
title, // notification title
message, // notification message
pushData, // the pushData you sent, as a JSON string — JSON.parse(row.pushData)
read, // this subscriber's read state — authoritative with perNotificationRead
}