# Create Indie Notification Inbox

This guide covers how to use the getIndieNotificationInbox function to get indie push notification data to be used to create your Indie Notification Inbox.

This video walks through the setup guide below:

[YouTube video player](https://www.youtube.com/embed/FPcqnl3Qbfc?rel=0)

## Indie Notification Inbox Preview

Here's how the Indie Notification Inbox creation process works:

- **Get Indie Notifications Sent:** Use the native-notify **getIndieNotificationInbox** function to get the history of notifications a subscriber received — the indie pushes sent to them plus your mass pushes (kept for 365 days).
- **Use**: Use the Indie Notification Inbox data from the **getIndieNotificationInbox** function to create an Indie Notification Inbox.

## Prerequisites

1. **Create a free Native Notify account**

   Create a free [NativeNotify.com](https://dashboard.nativenotify.com/sign-up-one)
   account to get your Native Notify App ID and App Token.

> **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](/docs/billing#what-each-plan-includes).

## Setup and Use

1. **Import**

   In your Indie Notification Inbox screen, include these imports:

   ```bash
   import React, { useState, useEffect } from 'react';
   import { getIndieNotificationInbox } from 'native-notify';
   ```

2. **Check for Hook function**

   Make sure you are using a Hook function as your Notification Inbox function. Here is an example:

   ```bash
   export default function NotificationInbox() {
       ...
   }
   ```

   This link explains Hooks in detail: [https://react.dev/reference/react/hooks](https://react.dev/reference/react/hooks)

3. **Create a data Hook**

   Inside of your Indie Notification Inbox screen, create a useState to store your Indie Notification Inbox data. Here's an example:

   ```bash
   const [data, setData] = useState([]);
   ```

4. **Get notifications**

   Paste this code into your Notification Inbox function:

   ```bash
   const [takeNumber, setTakeNumber] = useState(10);
   const [skipNumber, setSkipNumber] = useState(0);

   const fetchNotifications = async () => {
       try {
           const notifications = await getIndieNotificationInbox(
               "unique-user-id",
               "app-id-number",
               "app-token-string",
               takeNumber,
               skipNumber
           );
           console.log("notifications: ", notifications);
           setData(notifications);
       } catch (error) {
           console.error("Error fetching notifications: ", error);
       }
   };

   useEffect(() => {
     fetchNotifications();
   }, []);
   ```

   **Notes:**

   - Replace "unique-user-id" with the unique user ID you registered with Native Notify.
   - If you omit the take count, native-notify v5+ asks for 20 rows (v4, and a raw REST call without `take`, get the server's default of 10).
   - Skip count defaults to 0.
   - Each row's `pushData` is the JSON string you sent — `JSON.parse(row.pushData)` to read it.
   - Fetching marks every notification of that subscriber as read.
   - This link explains how 'useEffect' works in detail: [https://react.dev/reference/react/useEffect](https://react.dev/reference/react/useEffect)

5. **Create your Indie Notification Inbox**

   Create your Indie Notification Inbox using the data received by running your
   **getIndieNotificationInbox** function.

## Complete example

```bash
import React, { useState, useEffect } from 'react';
import { getIndieNotificationInbox } from 'native-notify';

export default function NotificationInbox() {
    const [data, setData] = useState([]);
    const [takeNumber, setTakeNumber] = useState(10);
    const [skipNumber, setSkipNumber] = useState(0);

    const fetchNotifications = async () => {
        try {
            const notifications = await getIndieNotificationInbox(
                "unique-user-id",
                "app-id-number",
                "app-token-string",
                takeNumber,
                skipNumber
            );
            console.log("notifications: ", notifications);
            setData(notifications);
        } catch (error) {
            console.error("Error fetching notifications: ", error);
        }
    };

    useEffect(() => {
      fetchNotifications();
    }, []);

    return (
      ... // create an Indie Notification Inbox using getIndieNotificationInbox data
    )
}
```

## Exact pagination: getIndieNotificationInboxPage

`getIndieNotificationInboxPage(subId, appId, appToken, take, skip)` returns one page of rows **plus** the server's total row count, read from the `X-Total-Count` response header — so "load more" can be exact instead of a guess:

```js
import { getIndieNotificationInboxPage } from 'native-notify';

const { rows, total } = await getIndieNotificationInboxPage(
  "unique-user-id",
  "app-id-number",
  "app-token-string",
  20, // take
  0   // skip
);

console.log(rows.length, "of", total); // total is null when the server doesn't send the header
```

> **Heads-up:**
>
> On the indie endpoint the `X-Total-Count` header reports **your subscriber's own** row total, so `total` is exact and page math can rely on it. `getIndieNotificationInbox(...)` returns the same rows without the total.

## Per-notification read state

Requires **native-notify v5.2+**. The page function takes an optional sixth argument: `{ perNotification: true }` — it adds `?perNotification=true` to the request, so each row keeps its own `read` flag and the server no longer marks the whole inbox read on fetch:

```js
import { getIndieNotificationInboxPage, markIndieNotificationRead } from 'native-notify';

const { rows, total } = await getIndieNotificationInboxPage(
  "unique-user-id",
  "app-id-number",
  "app-token-string",
  20, // take
  0,  // skip
  { perNotification: true }
);

rows[0].read; // this subscriber's own read state

// Mark ONE row read when it is opened (best-effort — resolves false, never throws).
// Pass the ids unless you set them once with NativeNotify.init():
await markIndieNotificationRead(rows[0].notification_id, "unique-user-id", "app-id-number", "app-token-string");
```

Read state is tracked **per subscriber** (`subId`), so — unlike [mass](/docs/mass-notification-inbox/getNotificationInbox#per-notification-read-state) — it works on simulators, in Expo Go, and on web. Without the option the request is byte-identical to previous SDK versions.

`markIndieNotificationRead(notificationId, subId, appId?, appToken?)` resolves `true` when the server accepted the mark, and `false` — never throwing — on any failure. Without a `notificationId` it resolves `false` and sends nothing (the endpoint's id-less form marks the whole inbox read).

The `useNotificationInbox()` hook and the prebuilt components expose the same feature as the [`perNotificationRead` prop](/docs/indie-notification-inbox/components#per-notification-read-state).

## Query the endpoint directly (REST / Postman)

Every inbox screen ultimately calls the public indie inbox endpoint behind `getIndieNotificationInbox` — you can call it yourself from Postman, curl, or any HTTP client when you need to verify what a subscriber's inbox actually contains:

```bash
# List a subscriber's notifications (newest first)
GET https://app.nativenotify.com/api/indie/notification/inbox/{subId}/{appId}/{appToken}?take=10&skip=0

# Unread count for the same subscriber
GET https://app.nativenotify.com/api/indie/notification/inbox/read/{subId}/{appId}/{appToken}
```

Replace `{subId}` with the unique user id you registered, and `{appId}` / `{appToken}` with your app's id number and app token — the same values you pass to `getIndieNotificationInbox`. `take` (default 10) and `skip` (default 0) paginate; add `perNotification=true` to keep each row's own `read` flag instead of the legacy mark-all-read on fetch. You can also mark read explicitly with `POST /api/indie/notification/inbox/read` and a JSON body of `{ "appId": …, "appToken": …, "subId": …, "notificationId": … }` (`notificationId` optional — without it, everything for the subscriber is marked read).

**Success is `201`** with a JSON array (`pushData` is the JSON string you sent):

```json
[
  {
    "notification_id": 12345678,
    "date": "9-17-2026 7:24PM",
    "title": "New comment",
    "message": "Someone commented on your post",
    "pushData": "{\"postId\":\"a1b2c3\"}",
    "read": true
  }
]
```

A plain `GET` keeps the legacy behavior — it marks every notification of the subscriber read *before* listing, so each row comes back `"read": true`. Add `perNotification=true` to list the real read state without marking anything.

The unread endpoint answers `201` with `{ "unreadCount": 3 }`.

On a **Premium** account these endpoints (and the mark-read call) answer `201` with this text instead — nothing is listed, counted or marked:

```text
Your current Native Notify membership cannot access notification inbox features. You must upgrade to a higher membership to access notification inbox features.
```

Check the body is a JSON array before using it. The inbox is a Pro plan feature — see [what each plan includes](/docs/billing#what-each-plan-includes).

**Responses are never cached** — every fetch returns the current rows.

**Troubleshooting an empty inbox:** the list contains the notifications sent to that exact `subId` plus the app's mass notifications sent after it registered — an empty array `[]` means nothing has reached that subscriber yet (compare the id you registered with the one you send to). A `401` with "The App ID and App Token do not match any app registered with Native Notify…" means the `appId` / `appToken` pair is wrong — re-copy both from your app's dashboard.
