> This page is part of the [Customer.io documentation](https://docs.customer.io). For the complete index, see [llms.txt](https://docs.customer.io/llms.txt).
> Last updated: June 26, 2026

# Display inbox messages in your app

This page is part of the [build-your-own inbox](/journeys/channels/in-app/inbox/advanced/overview/) approach. Once you’re familiar with the [message payload](/journeys/channels/in-app/inbox/advanced/overview/#the-message-payload), use the JavaScript SDK’s `inbox()` API to fetch messages and render them in your own client.

## 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/channels/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/channels/in-app/inbox/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

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/channels/in-app/inbox/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

```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

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

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';
```

## Report metrics

Because you render the inbox yourself, you also report engagement yourself with `inbox` methods. When a recipient sees a message, you should mark it opened; when they click a link or button in the message, you should report the click as a metric.

*   `markOpened()`: reports the message as `delivered` and `opened`.
*   `markUnopened()`: marks the message as unopened (it stays `delivered`).
*   `markDeleted()`: removes the message from the inbox.
*   `trackClick(actionName?)`: reports a `clicked` metric. Pass an optional action name if your message has more than one link or button.

See [inbox message metrics](/journeys/channels/in-app/inbox/metrics/) for what each metric means and when it’s reported.

## 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.

## Troubleshooting

Inbox messages follow the same [delivery patterns](/journeys/channels/in-app/getting-started/#in-app-warming-and-polling) as regular in-app messages. Check our [in-app messaging FAQ](/journeys/channels/in-app/faq/) for additional troubleshooting.

### My message doesn’t appear

1.  Verify that [you’ve enabled in-app and inbox messaging](/journeys/channels/in-app/getting-started/) in workspace settings.
2.  Ensure the In-App Plugin loads (`analytics.inbox` should be defined).
3.  Check that the message isn’t filtered out by `analytics.inbox()`. If you call `analytics.inbox('topic1')` and the message’s topic is `topic2`, it won’t appear in the inbox.
4.  Check that the user is [identified](/integrations/data-in/connections/javascript/method-reference/#identify).
5.  Confirm that the message you intend for people to see hasn’t expired. You may need to investigate a specific deliveryThe instance of a message sent to a person. When you set up a message, you determine an audience for your message. Each individual “send”—the version of a message sent to a single member of your audience—is a delivery. to see the expiration date.

### Topics don’t filter correctly

Remember that topics are set in the message template, not the API call. Messages with an empty array of `topics` match all topic filters. Calling `inbox('topic1')` only returns messages that include `'topic1'` in `topics`.

*   *   [Set up your inbox](#set-up-your-inbox)
        *   [Access the inbox](#access-the-inbox)
        *   [Get messages](#get-messages)
        *   [Notification inbox code example](#notification-inbox-code-example)
        *   [Import types for TypeScript](#import-types-for-typescript)
    *   [Report metrics](#report-metrics)
    *   [Best practices](#best-practices)
    *   [Troubleshooting](#troubleshooting)
        *   [My message doesn’t appear](#my-message-doesnt-appear)
        *   [Topics don’t filter correctly](#topics-dont-filter-correctly)

Copy page

Copy page [Download .md](/journeys/channels/in-app/inbox/advanced/display-messages.md)

Is this page helpful?

![](https://docs.customer.io/images/export-success.png) ![](https://docs.customer.io/images/export-failure.png)

# How can we make it better?

Close

Do you need help from Customer.io support?  No  
 Yes

What part of Customer.io do you need help with? 

How can we improve this page?

Email (optional):  Please provide a valid email address

 I am not a bot

 

We appreciate your feedback!

Our support team will contact you as soon as possible
