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 subId as read (not just the page it fetched), so the unread dot clears when the inbox opens — unless you opt into perNotificationRead below.
  • The app icon badge follows the unread count — whenever the unread count refreshes, it is synced to the app icon badge with Notifications.setBadgeCountAsync(). Pass syncBadge={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 read flag, 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:

PropTypeDefaultDescription
appIdnumber | string—Your Native Notify App ID.
appTokenstring—Your Native Notify App Token.
mode'mass' | 'indie''mass'Set to 'indie' for per-user notifications.
subIdnumber | string—Required when mode="indie" (the user's unique ID).
takenumber20Number of notifications fetched per page.
syncBadgebooleantrueSync the unread count to the app icon badge.
perNotificationReadbooleanfalsePer-row unread dots + mark-on-open, instead of marking the whole inbox read on fetch — see Per-notification read state.
colorsobjectlight/dark themePartial theme override — see Colors.
titlestring'Notifications'Inbox header title.
emptyTextstring"You're all caught up"Empty-state body text.
allowDeletebooleantrueShow 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.
showCountbooleanfalseShow a numeric unread badge instead of a plain dot.
maxCountnumber99Badge cap — shows "99+" past it.
renderIcon({ unreadCount, color }) => nodebundled bell iconCustom bell icon.
iconSizenumber24Bell icon size.
iconStyleStyleProp<ImageStyle>—Style override for the bell icon.
containerStyleStyleProp<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, badgeText
  • background, headerBackground, card, border
  • title, text, mutedText, emptyTitle, emptyText
  • accent, 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:

PropTypeDefaultDescription
visibleboolean—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
}