Skip to main content
@trycourier/courier-js exposes the Courier APIs for web browser-based applications.If you’re looking for full-featured UI components, check out Courier React or Courier UI Web Components.

Installation

Available on GitHub and npm.
npm install @trycourier/courier-js

Usage

Instantiate the Courier client.
const courierClient = new CourierClient({
    userId: 'my-user-id',
    jwt: 'eyJ.mock.jwt',
});

CourierClient options

OptionTypeRequiredDescription
userIdstringYesThe user to authenticate and whose messages should be fetched. This is the same user ID to which you should send a message.
jwtstringYesThe access token (JWT) minted for the user.
tenantIdstringNoOptional: The tenant to which messages should be scoped. See Inbox and Tenants for more information.
apiUrlsCourierApiUrlsNoOptional: REST/GraphQL and inbox GraphQL/WebSocket base URLs. Defaults to US hosts. For EU, see EU and regional endpoints at the end of this page. Type shape: CourierApiUrls under Models.
showLogsbooleanNoOptional: Enable debugging console logs from the Courier SDK. Defaults to process.env.NODE_ENV === 'development'.

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.

JWT Authentication Flow

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.
See all available user scopes for the Courier APIs.

Development Authentication with cURL

To quickly test JWT generation for development only, you can use cURL to call the Courier Issue Token Endpoint directly.
Do not call the Issue Token API from client-side code. Always keep your Courier API keys secure.
curl --request POST \
     --url https://api.courier.com/auth/issue-token \
     --header 'Accept: application/json' \
     --header 'Authorization: Bearer $YOUR_API_KEY' \
     --header 'Content-Type: application/json' \
     --data \
 '{
    "scope": "user_id:$YOUR_USER_ID write:user-tokens inbox:read:messages inbox:write:events read:preferences write:preferences read:brands",
    "expires_in": "$YOUR_NUMBER days"
  }'

Inbox APIs

Use the Courier Inbox APIs to read and update inbox messages. Pass an optional filter to getMessages() (and the same shape to getUnreadCounts() — see Unread Counts) to narrow results by tags, archive state, read status, or a creation time lower bound (from, an ISO 8601 datetime string).
import { CourierClient } from '@trycourier/courier-js';

const courierClient = new CourierClient({
    userId: 'my-user-id',
    jwt: 'eyJ.mock.jwt',
});

// Fetch inbox messages for the authenticated user
const inboxMessages = await courierClient.inbox.getMessages();

// Messages created on or after a given time (e.g. last 7 days)
const since = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
const recent = await courierClient.inbox.getMessages({
  filter: { from: since },
});

// Fetch archived messages for the authenticated user
const archivedMessages = await courierClient.inbox.getArchivedMessages();

// Get the message id and tracking ids for the first message
const { messageId, trackingIds } = inboxMessages.data.messages.nodes[0];

// Mark a message "clicked"
await courierClient.inbox.click({
    messageId,
    trackingId: trackingIds.clickTrackingId,
});

// Mark a message "read"
await courierClient.inbox.read({ messageId });

// Mark a message "unread"
await courierClient.inbox.unread({ messageId });

// Mark a message "opened"
await courierClient.inbox.open({ messageId });

// Archive a message
await courierClient.inbox.archive({ messageId });

// Unarchive a message
await courierClient.inbox.unarchive({ messageId });

// Mark all messages "read"
await courierClient.inbox.readAll();

// Archive all "read" messages
await courierClient.inbox.archiveRead();

// Archive all inbox messages
await courierClient.inbox.archiveAll();

Unread Counts

You can show unread badges or totals in a custom UI in three ways: a single global count, batched counts per filter (for tabs or feeds), or the unreadCount field returned with getMessages(). For live updates, connect the inbox WebSocket and refetch counts when message events arrive.

getUnreadMessageCount()

Returns a Promise<number> with the total number of unread inbox messages for the authenticated user. When you pass tenantId to CourierClient, the count is scoped to that tenant.
const unreadCount = await courierClient.inbox.getUnreadMessageCount();

getUnreadCounts(filtersMap)

Fetches unread counts for several named filters in one GraphQL request. Use this for per-tab or per-feed badges without multiple round trips.
ArgumentTypeDescription
filtersMapRecord<string, CourierGetInboxMessagesQueryFilter>Map from your own dataset ID (e.g. tab key) to the same filters you use with getMessages() (tags, archived, status, from).
Returns Promise<Record<string, number>> — the same keys as filtersMap, each mapped to an unread count. If a filter sets status: 'read', the client returns 0 for that key without calling the server (there are no unread messages in a read-only view).
const counts = await courierClient.inbox.getUnreadCounts({
  all: {},
  important: { tags: ['important'] },
  billing: { tags: ['billing'] },
});
// counts.all, counts.important, counts.billing
You can combine from with other fields so per-tab unread badges only count messages after a cutoff (same ISO 8601 string as getMessages()).
const since = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();

const counts = await courierClient.inbox.getUnreadCounts({
  last30Days: { from: since },
});

Unread count from getMessages()

getMessages() responses include both count (total messages matching the list filter) and unreadCount. Unless your filter already sets status, unreadCount reflects the unread subset that matches your tags, archived, from, and tenant constraints.
const response = await courierClient.inbox.getMessages({
  filter: { tags: ['important'] },
});

const unreadInImportantTab = response.data?.unreadCount;
const page = response.data?.messages;

Real-time updates

The SDK does not push a numeric unread total over the socket. Subscribe to inbox message events, then refetch counts (or reload your message list) when relevant events fire.
  1. Call courierClient.inbox.socket.connect() so the WebSocket is open.
  2. Register listeners with addMessageEventListener. It returns a function — call it to unsubscribe.
  3. Optionally compare envelope.event to InboxMessageEvent (exported from @trycourier/courier-js).
import {
  CourierClient,
  InboxMessageEvent,
  type InboxMessageEventEnvelope,
} from '@trycourier/courier-js';

const courierClient = new CourierClient({
  userId: 'my-user-id',
  jwt: 'eyJ.mock.jwt',
});

await courierClient.inbox.socket.connect();

const removeListener = courierClient.inbox.socket.addMessageEventListener(
  async (envelope: InboxMessageEventEnvelope) => {
    if (
      envelope.event === InboxMessageEvent.Read ||
      envelope.event === InboxMessageEvent.Unread ||
      envelope.event === InboxMessageEvent.NewMessage ||
      envelope.event === InboxMessageEvent.MarkAllRead
    ) {
      const updated = await courierClient.inbox.getUnreadMessageCount();
      // Update your UI (e.g. nav badge)
      console.log('Unread count:', updated);
    }
  }
);

// When you no longer need updates:
removeListener();
InboxMessageEvent includes: NewMessage ('message'), Read, Unread, Archive, ArchiveAll, ArchiveRead, Clicked, MarkAllRead, Opened, Unarchive, and Unopened. Listen to whichever events should trigger a refresh in your app.
Building with React? Courier React components and the useCourier() hook expose totalUnreadCount, per-tab unread counts, and real-time inbox updates without wiring the socket yourself. See CourierInboxPopupMenu and the useCourier() section on that page.

Preferences APIs

Use the Courier Preferences APIs to read and update user notification preferences per topic. See Preferences for more information.
// Get list of preferences
const preferences = await courierClient.preferences.getUserPreferences();

// Get a preference topic
const topic = await courierClient.preferences.getUserPreferenceTopic({
    topicId: 'HVS...'
});

// Update a preference topic
const topic = await courierClient.preferences.putUserPreferenceTopic({
    topicId: 'HVS...',
    status: 'OPTED_IN',               // 'OPTED_IN' | 'OPTED_OUT' | 'REQUIRED'
    hasCustomRouting: true,           // true | false
    customRouting: ['inbox', 'push'], // Array of: 'direct_message' | 'inbox' | 'email' | 'push' | 'sms' | 'webhook'
});

Brands APIs

Use the Courier Brands APIs to read custom brand settings. See Brands for more information.
// Gets a brand by id
const brand = await courierClient.brands.getBrand({
    brandId: 'YF1...'
});

Lists

Use the Courier Lists APIs to subscribe and unsubscribe users to lists. See Lists for more information.
// Subscribes the authenticated user to listId
await courierClient.lists.putSubscription({
    listId: 'your_list_id'
});

// Unsubscribes the authenticated user from listId
await courierClient.lists.deleteSubscription({
    listId: 'your_list_id'
});

Models

Inbox

InboxMessage

export interface InboxMessage {
    messageId: string;
    title?: string;
    body?: string;
    preview?: string;
    actions?: InboxAction[];
    data?: Record<string, any>;
    created?: string;
    archived?: string;
    read?: string;
    opened?: string;
    tags?: string[];
    trackingIds?: {
        archiveTrackingId?: string;
        openTrackingId?: string;
        clickTrackingId?: string;
        deliverTrackingId?: string;
        unreadTrackingId?: string;
        readTrackingId?: string;
    };
}

InboxAction

export interface InboxAction {
    content?: string;
    href?: string;
    data?: Record<string, any>;
    background_color?: string;
    style?: string;
}

CourierGetInboxMessagesQueryFilter

Optional filters for getMessages() and entries in getUnreadCounts(). Fields combine with AND logic.
export interface CourierGetInboxMessagesQueryFilter {
  tags?: string[];
  archived?: boolean;
  status?: 'read' | 'unread';
  /** ISO 8601 datetime; only messages created at or after this time are included */
  from?: string;
}

CourierApiUrls

Shape used for CourierClient apiUrls and for signIn in React / UI packages. For presets, use EU_COURIER_API_URLS or getCourierApiUrlsForRegion() — see EU and regional endpoints.
export interface CourierApiUrls {
  courier: {
    rest: string;
    graphql: string;
  };
  inbox: {
    graphql: string;
    webSocket: string;
  };
}

export type CourierApiRegion = 'us' | 'eu';

Preferences

CourierUserPreferencesTopic

export interface CourierUserPreferencesTopic {
    topicId: string;
    topicName: string;
    sectionId: string;
    sectionName: string;
    status: CourierUserPreferencesStatus;
    defaultStatus: CourierUserPreferencesStatus;
    hasCustomRouting: boolean;
    customRouting: CourierUserPreferencesChannel[];
}

CourierUserPreferencesStatus

export type CourierUserPreferencesStatus =
  | "OPTED_IN"
  | "OPTED_OUT"
  | "REQUIRED"
  | "UNKNOWN";

CourierUserPreferencesChannel

export type CourierUserPreferencesChannel =
  | "direct_message"
  | "inbox"
  | "email"
  | "push"
  | "sms"
  | "webhook"
  | "unknown";

Brands

CourierBrand

export interface CourierBrand {
    id: string;
    name: string;
    created: number;
    updated: number;
    published: number;
    version: string;
    settings?: CourierBrandSettings;
}

EU and regional endpoints

Most apps use Courier’s default US API and inbox hosts. Use this section only if your workspace is on the EU datacenter. By default, CourierClient uses getCourierApiUrlsForRegion('us') when you omit apiUrls. To target EU, use:
  • EU_COURIER_API_URLS — frozen preset (api.eu.courier.com, inbox.eu.courier.io, realtime.eu.courier.io)
  • getCourierApiUrlsForRegion(region) — returns a copy for 'us' or 'eu' (safe to tweak one host)
  • DEFAULT_COURIER_API_URLS — US preset (same as 'us')
import { CourierClient, EU_COURIER_API_URLS } from '@trycourier/courier-js';

const courierClient = new CourierClient({
  userId: 'my-user-id',
  jwt: 'eyJ.mock.jwt',
  apiUrls: EU_COURIER_API_URLS,
});
import { CourierClient, getCourierApiUrlsForRegion } from '@trycourier/courier-js';

const courierClient = new CourierClient({
  userId: 'my-user-id',
  jwt: 'eyJ.mock.jwt',
  apiUrls: getCourierApiUrlsForRegion('eu'),
});
Mint JWTs on your backend against the same region as the client: for EU, use https://api.eu.courier.com/auth/issue-token (see Create a JWT and EU Datacenter).
Courier React and Courier UI Web Components re-export these helpers — pass the same apiUrls into signIn({ ... }). See the Advanced section on the React page.
Check out the full list of Courier’s API endpoints.