Set up your notification inbox

Updated

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

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.

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
an example of an inbox message

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 for more information.
  2. 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 messages!

flowchart LR a{Have you added the
JS SDK to your site?} a-->|yes|b(Set up your inbox) a-.->|no|f(Add the JS SDK
to your website)-.->b b-->c{Does your inbox
expect anything more
than title and body?} c-->|yes|d(Tell your team
about payloads) c-.->|no|e d-->e(Send inbox messages)

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.

SDKMinimum required version
JavaScript SnippetN/A; automatically get latest version on page load
JavaScript import cdp-analytics-browser0.3.11

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.

{
  "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}}"
  }
}
FieldTypeRequired?Description
messageIdstringtrueUnique identifier for the message.
sentAtstringtrueWhen the message was sent.
expiresAtstringtrueWhen the message will expire.
openedbooleantrueIf true, the message has been opened.
topicsarrayfalseThe topics that the message belongs to.
typestringfalseThe type of message.
propertiesobjecttrueYour custom data payload representing the content you want to render. The payload cannot be empty.

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

If you haven’t already, you’ll need to set up in-app messaging. See getting started with in-app messages 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 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.

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.

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

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

Get Messages

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

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

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

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.

Copied to clipboard!
  Contents