Skip to main content
Courier React Inbox The Courier React SDK provides ready-made components and programmatic hooks for building notification experiences:
  • <CourierInbox /> — full-featured inbox for displaying and managing messages
  • <CourierInboxPopupMenu /> — popup menu version of the inbox
  • <CourierToast /> — toast notifications for time-sensitive alerts
  • useCourier() — hook for programmatic access and custom UIs
See these components in action with the interactive Inbox demo — no setup required.

Installation

Available on GitHub and npm. Courier publishes two React packages: @trycourier/courier-react for React 18+ and @trycourier/courier-react-17 for React 17.
npm install @trycourier/courier-react
This is the latest version of the Courier React SDK, recommended for new and existing apps.If you’re coming from an earlier version of the Courier React SDK, check out the v8 migration guide for what’s changed, how to upgrade your app, and links to documentation for past versions of the React SDK.
Not using React? Check out the @trycourier/courier-ui-inbox and @trycourier/courier-ui-toast packages instead, which provide Web Components for any JavaScript project.

Authentication

To use the SDK, you need to generate a JWT (JSON Web Token) for your user. This JWT should always be generated by your backend server, never in client-side code.
1

Your client calls your backend

When your app needs to authenticate a user, your client should make a request to your own backend (ex. GET https://your-awesome-app.com/api/generate-courier-jwt).
2

Your backend calls Courier

In your backend endpoint, use your Courier API Key to call the Courier Issue Token Endpoint and generate a JWT for the user.
3

Your backend returns the JWT to your client

Having received the JWT from Courier, your backend should return it to your client and pass it to the Courier SDK.
For a step-by-step walkthrough of authentication and token generation, see our JWT authentication tutorial.
Development testing with cURL To quickly test JWT generation for development only, you can call the Issue Token Endpoint directly.
Do not call the Issue Token API from client-side code. Always keep your Courier API keys secure.
curl -X POST https://api.courier.com/auth/issue-token \
  -H 'Authorization: Bearer $YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "scope": "user_id:$YOUR_USER_ID inbox:read:messages inbox:write:events",
    "expires_in": "1 day"
  }'

Quick Start

Get up and running with Courier React in minutes. This minimal example shows how to add the inbox component to your app.
import { useEffect } from "react";
import { CourierInbox, useCourier } from "@trycourier/courier-react";

export default function App() {
  const courier = useCourier();

  useEffect(() => {
    // Generate a JWT for your user on your backend server
    const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";

    // Authenticate the user
    courier.shared.signIn({
      userId: $YOUR_USER_ID,
      jwt: jwt,
    });
  }, []);

  return <CourierInbox />;
}
Follow the inbox implementation tutorial for detailed step-by-step guidance including backend JWT generation.

Inbox Component

<CourierInbox />
Preview of the default CourierInbox component
import { useEffect } from "react";
import { CourierInbox, useCourier } from "@trycourier/courier-react";

export default function App() {
  const courier = useCourier();

  useEffect(() => {
    const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
    courier.shared.signIn({ userId: $YOUR_USER_ID, jwt });
  }, []);

  return <CourierInbox />;
}
If you’re using tenants, scope requests to a particular tenant by passing its ID to signIn:
courier.shared.signIn({ userId: "my-user-id", jwt, tenantId: "my-tenant-id" });
For the full reference of sign in parameters, see the Courier JS docs.
Getting Started: Follow the step-by-step inbox implementation tutorial, or see a complete working example in our React Inbox sample app.

<CourierInboxPopupMenu />
Preview of the default CourierInboxPopupMenu component
import { useEffect } from "react";
import { CourierInboxPopupMenu, useCourier } from "@trycourier/courier-react";

export default function App() {
  const courier = useCourier();

  useEffect(() => {
    const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
    courier.shared.signIn({ userId: $YOUR_USER_ID, jwt });
  }, []);

  return (
    <div style={{ padding: "24px" }}>
      <CourierInboxPopupMenu />
    </div>
  );
}

Tabs and Feeds Tabs and feeds organize and filter messages in the inbox. A feed is a container that groups related tabs together. Each tab applies filters to show relevant messages.
If there is only one feed, the feed selection dropdown is hidden. If a feed has only one tab, the tab bar is hidden and the unread count appears next to the feed.
Filter options for each tab:
Filter PropertyTypeDescription
tagsstring[]Messages that have any of the specified tags
archivedbooleanWhether to include archived messages (defaults to false if unset)
status'read' | 'unread'Filter by read/unread status
Preview of CourierInbox with tabs
import { useEffect } from "react";
import {
  CourierInbox,
  useCourier,
  type CourierInboxFeed,
} from "@trycourier/courier-react";

export default function App() {
  const courier = useCourier();

  useEffect(() => {
    const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
    courier.shared.signIn({ userId: $YOUR_USER_ID, jwt });
  }, []);

  const feeds: CourierInboxFeed[] = [
    {
      feedId: 'notifications',
      title: 'Notifications',
      tabs: [
        { datasetId: 'all-notifications', title: 'All', filter: {} },
        { datasetId: 'unread-notifications', title: 'Unread', filter: { status: 'unread' } },
        { datasetId: 'important', title: 'Important', filter: { tags: ['important'] } },
        { datasetId: 'archived', title: 'Archived', filter: { archived: true } }
      ]
    }
  ];

  return <CourierInbox feeds={feeds} />;
}
You can also define multiple feeds to organize messages into different categories. Each feed appears as a selectable option in the inbox header.
Preview of CourierInbox with feeds

Handle Clicks and Presses
Callback PropType Signature
onMessageClick(props: CourierInboxListItemFactoryProps) => void
onMessageActionClick(props: CourierInboxListItemActionFactoryProps) => void
onMessageLongPress(props: CourierInboxListItemFactoryProps) => void
onMessageLongPress is only applicable on devices that support touch events.
import { useEffect } from "react";
import {
  CourierInbox,
  useCourier,
  type CourierInboxListItemFactoryProps,
  type CourierInboxListItemActionFactoryProps
} from "@trycourier/courier-react";

export default function App() {
  const courier = useCourier();

  useEffect(() => {
    const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
    courier.shared.signIn({ userId: $YOUR_USER_ID, jwt });
  }, []);

  return (
    <CourierInbox
      onMessageClick={({ message, index }: CourierInboxListItemFactoryProps) => {
        alert(`Message clicked at index ${index}:\n${JSON.stringify(message, null, 2)}`);
      }}
      onMessageActionClick={({ message, action, index }: CourierInboxListItemActionFactoryProps) => {
        alert(`Action clicked: ${JSON.stringify(action, null, 2)}`);
      }}
      onMessageLongPress={({ message, index }: CourierInboxListItemFactoryProps) => {
        alert(`Message long pressed at index ${index}`);
      }}
    />
  );
}

Styles and Theming Customize the inbox to match your app with a theme object. You can customize fonts, icons, text, and more.
Courier React Inbox with a custom unread indicator style
import { CourierInbox, type CourierInboxTheme } from "@trycourier/courier-react";

export default function App() {
  // Authentication code...

  const theme: CourierInboxTheme = {
    inbox: {
      header: {
        filters: { unreadIndicator: { backgroundColor: "#8B5CF6" } },
      },
      list: {
        item: { unreadIndicatorColor: "#8B5CF6" },
      },
    },
  };

  return <CourierInbox lightTheme={theme} darkTheme={theme} mode="light" />;
}
Theme utilities: defaultLightTheme / defaultDarkTheme provide the default inbox themes, and mergeTheme(baseTheme, overrideTheme) merges two themes with the override taking precedence.
import { defaultLightTheme, mergeTheme, type CourierInboxTheme } from "@trycourier/courier-react";

const customTheme: CourierInboxTheme = {
  inbox: { list: { item: { unreadIndicatorColor: "#8B5CF6" } } },
};

const mergedTheme = mergeTheme(defaultLightTheme, customTheme);
The full CourierInboxTheme type is below. Every property is optional; only override what you need. It covers the popup trigger button, the inbox window (header, feeds, tabs, actions), the message list (items, scrollbar, menus), and loading/empty/error states.
export type CourierInboxTheme = {
  popup?: {
    button?: {
      icon?: { color?: string; svg?: string };
      backgroundColor?: string;
      hoverBackgroundColor?: string;
      activeBackgroundColor?: string;
      unreadDotIndicator?: {
        backgroundColor?: string;
        borderRadius?: string;
        height?: string;
        width?: string;
      };
    };
    window?: {
      backgroundColor?: string;
      borderRadius?: string;
      border?: string;
      shadow?: string;
      animation?: {
        transition?: string;
        initialTransform?: string;
        visibleTransform?: string;
      };
    };
  };
  inbox?: {
    header?: {
      backgroundColor?: string;
      shadow?: string;
      border?: string;
      feeds?: {
        button?: {
          selectedFeedIconColor?: string;
          font?: { family?: string; weight?: string; size?: string; color?: string };
          changeFeedIcon?: { color?: string; svg?: string };
          unreadCountIndicator?: {
            font?: { family?: string; weight?: string; size?: string; color?: string };
            backgroundColor?: string;
            borderRadius?: string;
            padding?: string;
          };
          hoverBackgroundColor?: string;
          activeBackgroundColor?: string;
          transition?: string;
        };
        menu?: {
          backgroundColor?: string;
          border?: string;
          borderRadius?: string;
          shadow?: string;
          animation?: {
            transition?: string;
            initialTransform?: string;
            visibleTransform?: string;
          };
          list?: {
            font?: { family?: string; weight?: string; size?: string; color?: string };
            selectedIcon?: { color?: string; svg?: string };
            hoverBackgroundColor?: string;
            activeBackgroundColor?: string;
            divider?: string;
          };
        };
        tabs?: {
          borderRadius?: string | {
            topLeft?: string;
            topRight?: string;
            bottomLeft?: string;
            bottomRight?: string;
          };
          transition?: string;
          default?: {
            backgroundColor?: string;
            hoverBackgroundColor?: string;
            activeBackgroundColor?: string;
            font?: { family?: string; weight?: string; size?: string; color?: string };
            indicatorColor?: string;
            indicatorHeight?: string;
            unreadIndicator?: {
              font?: { family?: string; weight?: string; size?: string; color?: string };
              backgroundColor?: string;
              borderRadius?: string;
              padding?: string;
            };
          };
          selected?: {
            backgroundColor?: string;
            hoverBackgroundColor?: string;
            activeBackgroundColor?: string;
            font?: { family?: string; weight?: string; size?: string; color?: string };
            indicatorColor?: string;
            indicatorHeight?: string;
            unreadIndicator?: {
              font?: { family?: string; weight?: string; size?: string; color?: string };
              backgroundColor?: string;
              borderRadius?: string;
              padding?: string;
            };
          };
        };
      };
      tabs?: {
        borderRadius?: string | {
          topLeft?: string;
          topRight?: string;
          bottomLeft?: string;
          bottomRight?: string;
        };
        transition?: string;
        default?: {
          backgroundColor?: string;
          hoverBackgroundColor?: string;
          activeBackgroundColor?: string;
          font?: { family?: string; weight?: string; size?: string; color?: string };
          indicatorColor?: string;
          indicatorHeight?: string;
          unreadIndicator?: {
            font?: { family?: string; weight?: string; size?: string; color?: string };
            backgroundColor?: string;
            borderRadius?: string;
            padding?: string;
          };
        };
        selected?: {
          backgroundColor?: string;
          hoverBackgroundColor?: string;
          activeBackgroundColor?: string;
          font?: { family?: string; weight?: string; size?: string; color?: string };
          indicatorColor?: string;
          indicatorHeight?: string;
          unreadIndicator?: {
            font?: { family?: string; weight?: string; size?: string; color?: string };
            backgroundColor?: string;
            borderRadius?: string;
            padding?: string;
          };
        };
      };
      actions?: {
        button?: {
          icon?: { color?: string; svg?: string };
          backgroundColor?: string;
          hoverBackgroundColor?: string;
          activeBackgroundColor?: string;
        };
        markAllRead?: { icon?: { color?: string; svg?: string }; text?: string };
        archiveAll?: { icon?: { color?: string; svg?: string }; text?: string };
        archiveRead?: { icon?: { color?: string; svg?: string }; text?: string };
        animation?: {
          transition?: string;
          initialTransform?: string;
          visibleTransform?: string;
        };
        menu?: {
          backgroundColor?: string;
          border?: string;
          borderRadius?: string;
          shadow?: string;
          animation?: {
            transition?: string;
            initialTransform?: string;
            visibleTransform?: string;
          };
          list?: {
            font?: { family?: string; weight?: string; size?: string; color?: string };
            selectedIcon?: { color?: string; svg?: string };
            hoverBackgroundColor?: string;
            activeBackgroundColor?: string;
            divider?: string;
          };
        };
      };
    };
    list?: {
      backgroundColor?: string;
      scrollbar?: {
        trackBackgroundColor?: string;
        thumbColor?: string;
        thumbHoverColor?: string;
        width?: string;
        height?: string;
        borderRadius?: string;
      };
      item?: {
        unreadIndicatorColor?: string;
        backgroundColor?: string;
        hoverBackgroundColor?: string;
        activeBackgroundColor?: string;
        transition?: string;
        title?: { family?: string; weight?: string; size?: string; color?: string };
        subtitle?: { family?: string; weight?: string; size?: string; color?: string };
        time?: { family?: string; weight?: string; size?: string; color?: string };
        archiveIcon?: { color?: string; svg?: string };
        divider?: string;
        actions?: {
          backgroundColor?: string;
          hoverBackgroundColor?: string;
          activeBackgroundColor?: string;
          border?: string;
          borderRadius?: string;
          shadow?: string;
          font?: { family?: string; weight?: string; size?: string; color?: string };
        };
        menu?: {
          enabled?: boolean;
          backgroundColor?: string;
          border?: string;
          borderRadius?: string;
          shadow?: string;
          animation?: {
            transition?: string;
            initialTransform?: string;
            visibleTransform?: string;
          };
          longPress?: { displayDuration?: number; vibrationDuration?: number };
          item?: {
            hoverBackgroundColor?: string;
            activeBackgroundColor?: string;
            borderRadius?: string;
            read?: { color?: string; svg?: string };
            unread?: { color?: string; svg?: string };
            archive?: { color?: string; svg?: string };
            unarchive?: { color?: string; svg?: string };
          };
        };
      };
    };
    loading?: {
      animation?: {
        barColor?: string;
        barHeight?: string;
        barBorderRadius?: string;
        duration?: string;
      };
      divider?: string;
    };
    empty?: {
      title?: {
        font?: { family?: string; weight?: string; size?: string; color?: string };
        text?: string;
      };
      button?: {
        font?: { family?: string; weight?: string; size?: string; color?: string };
        text?: string;
        shadow?: string;
        border?: string;
        borderRadius?: string;
        backgroundColor?: string;
        hoverBackgroundColor?: string;
        activeBackgroundColor?: string;
      };
    };
    error?: {
      title?: {
        font?: { family?: string; weight?: string; size?: string; color?: string };
        text?: string;
      };
      button?: {
        font?: { family?: string; weight?: string; size?: string; color?: string };
        text?: string;
        shadow?: string;
        border?: string;
        borderRadius?: string;
        backgroundColor?: string;
        hoverBackgroundColor?: string;
        activeBackgroundColor?: string;
      };
    };
  };
};
Popup alignment and dimensions
Customizing the popup menu's alignment, position, and dimensions
Vertical AlignmentOptions
Top"top-right", "top-center", "top-left"
Center"center-right", "center-center", "center-left"
Bottom"bottom-right", "bottom-center", "bottom-left"
<CourierInboxPopupMenu
  popupAlignment="top-left"
  popupWidth="340px"
  popupHeight="400px"
  top="44px"
  left="44px"
/>
Fixed height: <CourierInbox /> has a default height of auto. Set a fixed height with the height prop:
<CourierInbox height="50vh" />

Custom Elements You can customize individual parts of the inbox by passing render props. Each render prop receives typed props and returns a ReactNode.
Render PropType Signature
renderListItem(props: CourierInboxListItemFactoryProps) => ReactNode
renderHeader(props: CourierInboxHeaderFactoryProps) => ReactNode
renderMenuButton(props: CourierInboxMenuButtonFactoryProps) => ReactNode
renderLoadingState(props: CourierInboxStateLoadingFactoryProps) => ReactNode
renderEmptyState(props: CourierInboxStateEmptyFactoryProps) => ReactNode
renderErrorState(props: CourierInboxStateErrorFactoryProps) => ReactNode
renderPaginationItem(props: CourierInboxPaginationItemFactoryProps) => ReactNode
You can also use React refs (CourierInboxElement, CourierInboxPopupMenuElement) for programmatic access to component methods like removeHeader().
Subsequent pages of messages are loaded automatically when the user scrolls to the bottom of the inbox, so the pagination component may only be visible briefly.

Toast Component

<CourierToast />
Toasts are short-lived notifications that notify users and prompt them to take action. The Toast component is connected to the feed of Courier Inbox messages.
Toasts are synced with the Inbox message feed. You can use both components together to provide persistent and temporary notifications.
import { useEffect } from "react";
import { CourierToast, useCourier } from "@trycourier/courier-react";

export default function App() {
  const courier = useCourier();

  useEffect(() => {
    const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
    courier.shared.signIn({ userId: $YOUR_USER_ID, jwt });
  }, []);

  return <CourierToast />;
}
Sample App: See a complete working example in our React Toast sample app.
Some initialization for toasts is asynchronous. If your app displays toasts immediately when the component is mounted, consider using the onReady callback (see props table below) to wait until the component is fully initialized.

Handle Clicks
Callback PropType Signature
onToastItemClick(props: CourierToastItemClickEvent) => void
onToastItemActionClick(props: CourierToastItemActionClickEvent) => void
If a message contains actions, toast items include a button for each. Use onToastItemActionClick to handle those clicks.
<CourierToast
  onToastItemClick={({ message }: CourierToastItemClickEvent) => {
    console.log("Toast clicked:", message);
  }}
  onToastItemActionClick={({ message, action }: CourierToastItemActionClickEvent) => {
    window.open(action.href);
  }}
/>

Styles and Theming
import { CourierToast, type CourierToastTheme } from "@trycourier/courier-react";

export default function App() {
  // Authentication code...

  const theme: CourierToastTheme = {
    toast: {
      item: {
        title: { color: "#6366f1", weight: "bold" },
        backgroundColor: "#edeefc",
        border: "1px solid #cdd1ff",
        borderRadius: "15px",
      },
    },
  };

  return <CourierToast lightTheme={theme} mode="light" />;
}
Toast theme utilities: defaultToastLightTheme / defaultToastDarkTheme for defaults, and mergeToastTheme(baseTheme, overrideTheme) to merge.
Custom Elements
Render PropType Signature
renderToastItemContent(props: CourierToastItemFactoryProps) => ReactNode — customize the content area only
renderToastItem(props: CourierToastItemFactoryProps) => ReactNode — fully replace each toast item

CourierToast Props
Prop NameTypeDefaultDescription
styleCSSProperties{ position: "fixed", width: "380px", top: "30px", right: "30px", zIndex: 999 }Styles applied to the toast component.
lightThemeCourierToastThemeundefinedTheme for light mode.
darkThemeCourierToastThemeundefinedTheme for dark mode.
mode"light" | "dark" | "system""system"Theme mode.
autoDismissbooleanfalseEnable auto-dismiss with countdown bar.
autoDismissTimeoutMsnumber5000Timeout in ms before auto-dismiss.
dismissButton"visible" | "hidden" | "hover" | "auto""auto"Dismiss button visibility. "auto" shows always if autoDismiss is false, on hover if true.
onToastItemClickfnundefinedCallback when a toast is clicked.
onToastItemActionClickfnundefinedCallback when a toast action button is clicked.
renderToastItemfnundefinedCustom render for entire toast items.
renderToastItemContentfnundefinedCustom render for toast item content only.
onReady(ready: boolean) => voidundefinedCallback when the component is ready to receive messages.
Enabling autoDismiss adds a countdown bar to each toast and automatically removes it after the timeout. The countdown bar color is theme-able via autoDismissBarColor in CourierToastTheme.
<CourierToast autoDismiss={true} autoDismissTimeoutMs={7000} />
Using onReady: If toasts display immediately on mount or custom render functions don’t apply correctly, use onReady to wait for full initialization before authenticating:
const [toastReady, setToastReady] = useState(false);

useEffect(() => {
  if (toastReady) {
    courier.shared.signIn({ userId: $YOUR_USER_ID, jwt });
  }
}, [toastReady]);

return <CourierToast onReady={setToastReady} renderToastItem={(props) => <CustomToast {...props} />} />;

useCourier Hook

The useCourier() hook provides programmatic access to Courier functionality for building custom UIs or integrating Courier features into existing components. When to use hooks vs components:
  • Components (<CourierInbox />, <CourierToast />): Quick integration with default UI
  • Hooks (useCourier()): Custom UIs, programmatic control, advanced state management
  • Both together: Use hooks for state management while components handle rendering
Hook return value:
{
  shared: Courier.shared,  // Direct access to Courier instance
  auth: {
    userId?: string,
    signIn: (props: CourierProps) => void,
    signOut: () => void
  },
  inbox: {
    // Methods
    load: (props?: { canUseCache?: boolean, datasetIds?: string[] }) => Promise<void>,
    fetchNextPageOfMessages: (props: { datasetId: string }) => Promise<InboxDataSet | null>,
    setPaginationLimit: (limit: number) => void,
    registerFeeds: (feeds: CourierInboxFeed[]) => void,
    listenForUpdates: () => Promise<void>,
    readMessage: (message: InboxMessage) => Promise<void>,
    unreadMessage: (message: InboxMessage) => Promise<void>,
    clickMessage: (message: InboxMessage) => Promise<void>,
    archiveMessage: (message: InboxMessage) => Promise<void>,
    unarchiveMessage: (message: InboxMessage) => Promise<void>,
    openMessage: (message: InboxMessage) => Promise<void>,
    readAllMessages: () => Promise<void>,
    // Reactive state
    feeds: Record<string, InboxDataSet>,
    totalUnreadCount?: number,
    error?: Error
  },
  toast: {
    addMessage: (message: InboxMessage) => void,
    removeMessage: (message: InboxMessage) => void,
    error?: Error
  }
}
Complete example — authentication, inbox setup, real-time updates, and displaying messages:
You must call inbox.listenForUpdates() after authentication to enable real-time message updates. Without this, the inbox only shows messages from the initial load.
import { useEffect } from "react";
import { useCourier, type InboxMessage, defaultFeeds } from "@trycourier/courier-react";

export default function App() {
  const { auth, inbox } = useCourier();

  useEffect(() => {
    auth.signIn({
      userId: $YOUR_USER_ID,
      jwt: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    });
    loadInbox();
  }, []);

  async function loadInbox() {
    inbox.registerFeeds(defaultFeeds());
    await inbox.listenForUpdates();
    await inbox.load();
  }

  return (
    <div>
      <div>Total Unread: {inbox.totalUnreadCount ?? 0}</div>
      <ul>
        {inbox.feeds['all_messages']?.messages.map((message: InboxMessage) => (
          <li key={message.messageId} style={{
            backgroundColor: message.read ? 'transparent' : '#fee2e2',
            padding: '8px', marginBottom: '4px'
          }}>
            {message.title}
          </li>
        ))}
      </ul>
    </div>
  );
}
Inbox methods:
MethodDescription
inbox.load(props?)Load messages. Optional { canUseCache, datasetIds }.
inbox.fetchNextPageOfMessages({ datasetId })Fetch next page. Returns InboxDataSet | null.
inbox.setPaginationLimit(limit)Set messages per page.
inbox.registerFeeds(feeds)Register feeds and tabs with the datastore.
inbox.listenForUpdates()Start WebSocket connection for real-time updates. Required after auth.
inbox.readMessage(message)Mark as read.
inbox.unreadMessage(message)Mark as unread.
inbox.archiveMessage(message)Archive.
inbox.unarchiveMessage(message)Unarchive.
inbox.clickMessage(message)Track click event (analytics).
inbox.openMessage(message)Mark as opened.
inbox.readAllMessages()Mark all as read.
Toast methods:
MethodDescription
toast.addMessage(message)Add a message to the toast stack.
toast.removeMessage(message)Remove a message from the toast stack.
Error handling: Check inbox.error and toast.error for error states:
const { inbox } = useCourier();
if (inbox.error) return <div>Error: {inbox.error.message}</div>;
Sample Apps: See complete working examples in our React Hooks sample app and React Inbox sample app.

Advanced

Next.js and SSR Courier Inbox and Toast support Next.js but only render client-side. In Next.js 13+, add 'use client' to the top of any file using Courier components.
"use client"

import { CourierInbox } from "@trycourier/courier-react";

export default function Page() {
  // Authentication code...
  return <CourierInbox />;
}

Troubleshooting
Make sure you’ve called inbox.listenForUpdates() after authentication. This establishes the WebSocket connection required for real-time updates.
await auth.signIn({ userId, jwt });
inbox.registerFeeds(defaultFeeds());
await inbox.listenForUpdates(); // Required for real-time
await inbox.load();
Possible causes:
  1. Not authenticated — ensure signIn() has been called
  2. Feeds not registered — call registerFeeds() before load()
  3. Network errors — check inbox.error for details
  4. JWT expired — generate a new token
  • Invalid JWT: Ensure the JWT is generated correctly on your backend
  • Expired JWT: JWTs have an expiration time; generate a new one
  • Missing scopes: Ensure your JWT includes inbox:read:messages and inbox:write:events
  • Wrong user ID: Verify the userId matches the user the JWT was issued for
Import types directly from the package:
import { useCourier, type InboxMessage, type CourierInboxFeed } from '@trycourier/courier-react';
Ensure you’re using the correct package:
  • React 18+: @trycourier/courier-react
  • React 17: @trycourier/courier-react-17

Best Practices
  • JWT Security: Always generate JWTs server-side. Cache on the client, refresh before expiration (standard: '1d'), include only necessary scopes.
  • Performance: Use canUseCache: true (default) for cached data. Use datasetIds to load only needed datasets. Set appropriate setPaginationLimit() values. The hook’s reactive state updates automatically; avoid triggering unnecessary re-renders.
  • Testing: Mock useCourier() in tests to return test data:
jest.mock('@trycourier/courier-react', () => ({
  useCourier: () => ({
    inbox: {
      feeds: { 'all_messages': { messages: mockMessages } },
      totalUnreadCount: 5
    }
  })
}));

TypeScript Types