Display inbox messages in your app
UpdatedThis page is part of the build-your-own inbox approach. Once you’re familiar with 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 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';
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.
your website or app?} b-.->|no, wait until they return to your website or app|b b-->|yes|c{Did they open
the inbox?} c-->|yes|d(Message is displayed
to the user) d-->f{Does the inbox send
markOpened when themessage is displayed?} f-->|yes|g(Message is opened) f-.->|no|h(Message is not opened) c-.->|no|e{Is the message expired?} e-->|no, wait for the user
to open the inbox|c e-..->|yes|i(Message expires and
is removed from the inbox)
markOpened(): reports the message asdeliveredandopened.markUnopened(): marks the message as unopened (it staysdelivered).markDeleted(): removes the message from the inbox.trackClick(actionName?): reports aclickedmetric. Pass an optional action name if your message has more than one link or button.
See inbox message 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 as regular in-app messages. Check our in-app messaging FAQ for additional troubleshooting.
My message doesn’t appear
- Verify that you’ve enabled in-app and inbox messaging in workspace settings.
- Ensure the In-App Plugin loads (
analytics.inboxshould be defined). - Check that the message isn’t filtered out by
analytics.inbox(). If you callanalytics.inbox('topic1')and the message’s topic istopic2, it won’t appear in the inbox. - Check that the user is identified.
- 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.
