# Set up your notification inbox

The notification inbox lets you send messages to your audience that they can access at their leisure.

 We’re actively working on this feature

We’re just getting started with inbox messaging. For now, the feature requires some extra work on your part to build your own inbox, style messages in your client, and listen for events. We’re working on features to make this all easier.

## How it works[](#how-it-works)

Inbox messages aren’t displayed immediately (or along our normal prioritization settings for in-app messages), but rather are displayed through a custom inbox UI. Your audience typically sees them when they open the inbox in your app or on your website. Check out Colby’s video below for a quick overview and demonstration of the feature.

Inbox messages differ from other messages in two important ways:

1.  Unlike other message channels, messages aren’t “delivered” in the traditional sense. Instead, you’ll fetch them when people visit your site.
2.  Inbox messages aren’t delivered as rendered messages. They’re delivered as JSON payloads.

So when people visit your site, you’ll fetch messages. Then when people open the inbox, they’ll see an interact with messages.

[![an example of an inbox message](https://docs.customer.io/images/inbox-message-example.png)](#c67a0f14eadaa53ff9b8ce2127897439-lightbox)

### General setup process[](#general-setup-process)

Before you can send inbox messages, you’ll need to do a few things first:

1.  Make sure you’re set up with our JavaScript SDK on your website. See our [in-app messaging documentation](/journeys/in-app-getting-started/) for more information.
2.  [Set up your inbox](#set-up-your-inbox).
3.  Tell your team members what payloads, types, and topics to use in messages: because inbox messages are delivered as JSON payloads, your coworkers—anybody who uses Customer.io to send inbox messages—needs to know what to send in messages.

When you’re done, you’re ready to [send messages](/journeys/send-inbox/) messages!

### Minimum supported versions[](#minimum-supported-versions)

To support the notification inbox, you need to use of our client SDKs. See the table below for minimum version requirements.

If you use our JavaScript snippet, you don’t need to worry about the version requirements. We’ll automatically use the latest version of the SDK when people visit your website. But any other import method needs to use a version of the SDK that supports the inbox feature.

The examples on this page are for our JavaScript package. See the documentation for your mobile SDK for help setting up the inbox in your mobile app.

SDK

Minimum required version

JavaScript Snippet

N/A; automatically get latest version on page load

JavaScript import [cdp-analytics-browser](/integrations/data-in/connections/javascript/frameworks/)

0.3.11

[iOS](/integrations/sdk/ios/in-app/inbox/)

4.2

[Android](/integrations/sdk/android/in-app/inbox/)

4.16

[React Native](/integrations/sdk/react-native/in-app-messages/inbox/)

6.2

[Flutter](/integrations/sdk/flutter/in-app-messages/inbox/)

3.3

[Expo](/integrations/sdk/expo/in-app-messages/inbox/)

3.1

## The message payload[](#the-message-payload)

Inbox messages are delivered as a JSON payload. Our SDKs help you listen for the payload and render messages in your inbox.

By default,messages contain a `properties` object with a `title` and `body`, but you can send arbitrary JSON—whatever you set up your inbox to expect. So, when you set up your inbox, make sure that your team members understand the structure of the payload—which fields are required, and which topics or types of messages your inbox expects.

```json
{
  "messageId": "1234567890",
  "sentAt": "2026-02-05T12:00:00Z",
  "opened": false,
  "topics": ["orders", "shipping"],
  "type": "order_shipped",
  "properties": {
    "title": "Hey, {{customer.first_name}}, your order shipped!",
    "body": "You can track your order #{{event.order_number}} here:",
    "link": "https://example.com/orders/{{event.order_number}}"
  }
}
```

Field

Type

Required?

Description

`messageId`

string

true

Unique identifier for the message.

`sentAt`

string

true

When the message was sent.

`expiresAt`

string

true

When the message will expire.

`opened`

boolean

true

If `true`, the message has been opened.

`topics`

array

false

The topics that the message belongs to.

`type`

string

false

The type of message.

`properties`

object

true

Your custom data payload representing the content you want to render. The payload cannot be empty.

### Topics and Types[](#topics-and-types)

The `topic` field acts as a way to filter messages when you call the `inbox()` method. But, because these two values are also included in the message payload, you can use the `topic` and `type` fields to help you render messages.

For example, you might have `orders` and `sale` topics, where `orders` don’t have images but `sale` topics might. Or, within the `orders` topic, you might have `order_placed` and `order_shipped` types, where `order_placed` lists order details and images of purchased products and `order_shipped` provides a link to the tracking information for the order that opens in a new tab.

## Set up your inbox[](#set-up-your-inbox)

If you haven’t already, you’ll need to set up in-app messaging. See [getting started with in-app messages](/journeys/in-app-getting-started/) for more information.

On your website, you’ll use our JavaScript SDK’s `inbox()` API to retrieve and manage inbox messages. When you’re done setting up your inbox, you’re ready to [send messages](/journeys/send-inbox-txnl/) and display them to your audience.

Our SDKs support the following methods:

*   `total()` / `totalUnopened()`: Get the total number of messages, or the number of unopened messages in the inbox.
*   `markOpened()` / `markUnopened()`: Determine whether the message was opened or not.
*   `markDeleted()`: Delete the message.
*   `trackClick(actionName?)`: Track click metrics. If your message has a button or a link, you can use this method to indicate a click on the message.

### Access the inbox[](#access-the-inbox)

When you fetch messages, you can filter by the “topics” that the message belongs to. This lets you—or your audience—determine the messages the inbox displays. You’ll set the topics that a message belongs to when you [send the message](/journeys/send-inbox/).

If you don’t allow users to filter messages, and you just want to display all messages to users, you can fetch all inbox messages without parameters.

```javascript
// Get all inbox messages
const inbox = analytics.inbox();

// Filter by topic
const orderInbox = analytics.inbox('orders', 'shipping');
```

### Get Messages[](#get-messages)

```javascript
// Get all messages
const messages = await inbox.messages();

// Get counts
const total = await inbox.total();
const unopened = await inbox.totalUnopened();

// Subscribe to updates
const unsubscribe = inbox.onUpdates((messages) => {
  console.log('Inbox updated!', messages);
  // Update your UI
});
```

### Notification inbox code example[](#notification-inbox-code-example)

Here’s a simple example for an inbox UI. This example assumes you’ve already set up your backend to trigger inbox messages and you’ve already loaded the Customer.io JavaScript SDK.

```javascript
// Cache messages and inbox instance to avoid re-fetching in each handler.
// Alternatively, you could call inbox.messages() in handlers for simpler code
// at the cost of an extra async call on each button click.
let currentMessages = [];
let inboxInstance;

// Helper to escape HTML and prevent XSS
function escapeHtml(text) {
  const div = document.createElement('div');
  div.textContent = text;
  return div.innerHTML;
}

// Render function
function renderInbox(messages) {
  currentMessages = messages; // Store for handlers
  const container = document.getElementById('inbox');
  
  container.innerHTML = messages.map(message => {
    const props = message.properties;
    return `
      <div class="inbox-message ${message.opened ? '' : 'unread'}">
        <div class="inbox-message-content">
          <h4>${escapeHtml(props.title)}</h4>
          <p>${escapeHtml(props.body)}</p>
          <small>${new Date(message.sentAt).toLocaleDateString()}</small>
        </div>
        <button onclick="handleRead('${message.messageId}', ${message.opened})">
          ${message.opened ? 'Mark Unread' : 'Mark Read'}
        </button>
        <button onclick="handleDelete('${message.messageId}')">Delete</button>
      </div>
    `;
  }).join('');
}

// Update the unread badge
async function updateBadge() {
  if (inboxInstance) {
    const count = await inboxInstance.totalUnopened();
    document.getElementById('unread-badge').textContent = count;
  }
}

// Wait for analytics to be ready, then initialize inbox and load messages
cioanalytics.ready(() => {
  inboxInstance = cioanalytics.inbox('orders', 'announcements');
  
  inboxInstance.messages().then(renderInbox);
  inboxInstance.onUpdates(renderInbox);
  
  // Update unread badge
  updateBadge();
});

// Message actions
async function handleRead(messageId, isOpened) {
  const message = currentMessages.find(m => m.messageId === messageId);
  if (message) {
    isOpened ? await message.markUnopened() : await message.markOpened();
    await updateBadge();
  }
}

async function handleDelete(messageId) {
  const message = currentMessages.find(m => m.messageId === messageId);
  if (message) {
    await message.markDeleted();
    await updateBadge();
  }
}
```

### Import types for TypeScript[](#import-types-for-typescript)

If you use TypeScript or you use our `cdp-analytics-browser` package with a JavaScript framework like React, Vue, or something else, you can import types.

```typescript
import type { InboxAPI, InboxMessage } from '@customerio/cdp-analytics-browser';
```

## Best practices[](#best-practices)

**Initialize the inbox within the `cioanalytics.ready()` callback.** This ensures the Customer.io analytics SDK is fully loaded before you interact with the inbox. This is especially important if you’re using the inbox in a React component or single-page application.

**Update message state.** Mark messages as opened when displayed, track clicks to measure engagement, and delete dismissed messages to keep inboxes clean.

**Test thoroughly.** Before going live, verify your payload structure, confirm messages appear in your inbox API calls, test topic filtering, and check that state changes work correctly.