Push Notifications Vanish. Customer.io's Notification Inbox Is for the Messages That Shouldn't
In 1974, an engineer named Gordon Matthews was stuck in a client's office in Denver, repeatedly failing to get a call through to his own head office in Dallas. While he waited, he glanced at the wastebasket next to him. It was full of pink "While You Were Out" slips... handwritten notes telling so-and-so to ring so-and-so back, scrawled, binned, and already forgotten. He went home and told his wife he had a crazy idea. In 1979 he filed a 49-page patent application, and in 1980 he sold the first commercial voicemail system to 3M—his wife Monika recorded the first greeting. The Plainview Herald's obituary called him the father of voice mail; a Door County Pulse profile tells the wastebasket story in full.
Before Matthews, a missed call was simply gone. Voicemail won because it gave the message somewhere to wait.
Half a century later, most product messaging has quietly recreated the pre-voicemail problem. A push notification swiped away is gone; so is a dismissed in-app modal. If the message mattered—a billing warning, a security alert, credits about to expire—the user has no way back to it. On 19 February 2026, Customer.io shipped its answer: the notification inbox, a channel where messages wait until the user deals with them.
This post covers what the inbox actually is: JSON payloads, not rendered messages—a distinction that drives everything else. It gives you a decision rule for which messages belong in the inbox versus push, in-app and email, plus the payload contract your marketing and engineering teams need to agree before the first send. And it's honest about the build cost, because the feature is new and the docs say so plainly.
The channel where dismissed means deleted
Push and in-app messages share a failure mode: dismissal is deletion. Customer.io's own release note draws the line directly—unlike in-app messages or push notifications, "which are gone when your audience dismisses them", inbox messages give users "a way to see important messages at their leisure".
Dismissal happens constantly, and mostly without reading. A 2025 ACM study tracked 12,476 notifications and found users received an average of 89.1 per day. For teenagers it's worse: a 2023 study co-led by Michigan Medicine's Mott Children's Hospital and Common Sense Media put the figure at roughly 240 app notifications a day. Nobody reads 89 notifications. People clear them in batches, the way Matthews's colleagues binned pink slips—and your billing warning gets cleared with the rest.
For most messages, that's fine. A "someone liked your post" notification has no second life. But some message classes quietly break when dismissed-means-deleted. The order update a customer wants to check twice. The card-failure warning they meant to act on after lunch. The security notice they need to re-read, and the unused credits expiring at month end. Sending more push to compensate makes the problem worse, not better—we've written before about the art of not sending, and a louder channel is no substitute for a persistent one.
What the notification inbox actually is
The notification inbox is the only message channel your customers see that Customer.io doesn't render. Per the setup documentation, inbox messages differ from every other channel in two ways: they aren't "delivered" in the traditional sense—your site fetches them when people visit—and they arrive as JSON payloads, not rendered messages. You build the inbox UI. Customer.io supplies the data and the SDK methods to manage it.
That sounds like a limitation, and in the short term it is one. It's also the point. Because the payload is arbitrary JSON, the inbox renders in your product's own components, matches your design system exactly, and can carry whatever structured data your message types need—order numbers, deep links, images, severity levels. The message persists until it expires or the user deletes it, and it bypasses the normal prioritisation settings that govern regular in-app messages.
You can trigger inbox messages from a campaign or broadcast workflow, or send them as transactional messages from your backend—which fits, given the message classes the inbox suits best are mostly transactional in nature.
The payload contract
Every inbox message arrives with the same envelope. Five fields are required: messageId, sentAt, expiresAt, opened, and properties—your custom content object, which cannot be empty. Two optional fields, topics and type, handle filtering and rendering hints.
{
"messageId": "1234567890",
"sentAt": "2026-02-05T12:00:00Z",
"expiresAt": "2026-04-06T12:00:00Z",
"opened": false,
"topics": ["billing"],
"type": "card_failed",
"properties": {
"title": "Your card ending 4242 was declined",
"body": "Update your payment method before 20 February to keep your workspace active.",
"link": "https://example.com/settings/billing"
}
}
By default properties contains a title and body, but you can send whatever JSON your inbox expects. And that's exactly where teams get burnt: the schema of properties is an agreement between the marketer composing messages in Customer.io and the engineer who built the inbox client. Nothing enforces it. If marketing sends a cta_label field the client doesn't render, or omits the link the client requires, the message silently degrades.
So agree the contract before the first send. In practice that's a short document with four parts. List the topics (billing, orders, announcements) and the types within each topic (card_failed, card_expiring). Then define the required and optional properties fields per type, and name who signs off changes. Topics filter the inbox—calling analytics.inbox('billing') returns only billing messages—while type tells your client how to render each one. Two gotchas from the send documentation belong in the contract too: topics are set in the message template, not the API call, and a message with an empty topics array matches every topic filter. Liquid works in the payload, so {{customer.first_name}} and {{event.order_number}} personalise inbox messages the same way they personalise email.
The decision rule: inbox vs push vs in-app vs email
Four questions sort any message class into the right channel:
- Will the user need this message again after first seeing it? If yes, the inbox carries it—whatever else you also send.
- Does it need to interrupt someone right now? If yes, push is the interrupt. Push and inbox pair well: push for the tap on the shoulder, inbox for the copy that survives the swipe.
- Does it only make sense while they're inside the product? Feature tours, contextual nudges and upgrade prompts belong to in-app—we've covered which in-app triggers to configure first and when an inline message beats a modal.
- Do they need it when they can't sign in? A password reset is useless inside the product. Anything a locked-out, churned or offline user must receive goes to email.
Here's the test applied to common SaaS message classes:
| Message class | Push | In-app | Inbox | |
|---|---|---|---|---|
| Order shipped | Optional | No | Yes | Yes |
| Card payment failed | Yes | No | Yes | Yes |
| Security alert (new device login) | Yes | No | Yes | Yes |
| Credits expiring this month | No | Optional | Yes | Optional |
| Feature announcement | No | Yes | Optional | No |
| Onboarding nudge | No | Yes | No | Optional |
| Password reset | No | No | No | Yes |
| Weekly usage digest | No | No | Optional | Yes |
The pattern: account-critical messages run double-tracked (an interrupt plus a persistent copy), product-contextual messages stay in-app, and anything legal or lockout-related stays on email. The inbox doesn't replace a channel—it fills the persistence gap the others were never built for. For the wider architecture across email, SMS and push, see our omnichannel messaging strategy guide.
The honest build cost
The docs don't oversell this feature, and neither should you when pitching it internally. The setup page leads with a warning box: "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."
Translated: there is no drop-in widget. Marketing cannot switch this channel on alone—engineering builds the inbox UI first.
Version requirements come first. If you use the JavaScript snippet you get the latest SDK automatically, but package imports need cdp-analytics-browser 0.3.11 or later. The mobile SDK minimums are iOS 4.2, Android 4.16, React Native 6.2, Flutter 3.3 and Expo 3.1. If your app ships an older SDK, an upgrade lands on the roadmap before any inbox work starts.
The web build itself runs through the inbox() API. You fetch messages with inbox.messages(), drive an unread badge with total() and totalUnopened(), subscribe to changes with onUpdates(), and manage state per message with markOpened(), markUnopened(), markDeleted() and trackClick(). TypeScript types ship in the package (InboxAPI, InboxMessage). The docs include a worked example—a list view with read/unread state, delete buttons and a badge counter—which is a realistic floor for a first version. Initialise inside the cioanalytics.ready() callback, and escape your HTML: you're rendering payload strings into your own DOM now, so XSS hygiene is your job, not Customer.io's.
Given the "we're actively working on this" framing, build thin. A list, a badge, mark-as-read and delete covers the persistence problem. Don't gold-plate a custom rendering engine that Customer.io's roadmap may replace; put the effort into the payload contract instead, because the contract survives whatever UI tooling ships later. And if a message doesn't appear during testing, the failure points mirror regular in-app messages: SDK loaded, user identified, message not expired, topic filter not excluding it. It's the same family of silent failures we've mapped in the in-app troubleshooting decision tree.
Measuring it without fooling yourself
Inbox metrics have unusual semantics, and the metrics documentation is upfront about them. A message stays in the Sent state until the recipient returns to your site or app, opens the inbox, and your client reports the open. Stranger still: delivered is reported by your markOpened() call, together with opened—because for inbox messages, as the docs put it, delivery "doesn't really happen independently of visibility".
Three consequences for your reporting. First, time-to-open measures user behaviour, not send performance. A campaign whose inbox messages sit unopened for a week isn't underperforming... your users just haven't been back. Read open counts against site-visit frequency, not against the send date. Second, the available metrics—sent, delivered, opened, clicked and failed (typically a Liquid rendering issue)—look like email metrics but don't behave like them. Don't put inbox open rates next to email open rates on the same slide. Third, "opened" means whatever your client says it means. The docs recommend calling markOpened() when the message is displayed in the open inbox; call it lazily when the list renders and you've recreated the inflation problem in-app metrics have, where "opened" just means displayed. Define the trigger for markOpened() in the payload contract, and your metrics stay honest from day one.
Frequently asked questions
What is the Customer.io notification inbox?
The notification inbox is a message channel Customer.io released on 19 February 2026 that delivers messages to a persistent inbox in your app or website, rather than displaying them once and discarding them. Messages arrive as JSON payloads that your own inbox UI renders, and they stay available until they expire or the user deletes them. It's designed for messages users may need to revisit—order updates, billing warnings, security notices.
How is an inbox message different from an in-app message in Customer.io?
An in-app message is rendered by Customer.io and displayed immediately according to your prioritisation settings; once dismissed, it's gone. An inbox message is a JSON payload your own UI renders, it bypasses in-app prioritisation entirely, and it persists in the inbox until it expires or the user deletes it. They also report metrics differently: an inbox message isn't even marked delivered until your client reports it opened.
Do Customer.io inbox messages expire?
Yes. Every inbox message carries a required expiresAt field, and by default messages expire after 60 days—you can change the expiry window per message when you compose it. Messages only expire if they're sent but not delivered; once expired, they're removed from the inbox.
Which Customer.io SDK versions support the notification inbox?
The minimums are iOS 4.2, Android 4.16, React Native 6.2, Flutter 3.3, Expo 3.1, and cdp-analytics-browser 0.3.11 for JavaScript package imports. If you load Customer.io through the JavaScript snippet, there's no version to manage—the snippet fetches the latest SDK on page load.
Do I have to build my own inbox UI for Customer.io?
For now, yes. Customer.io delivers inbox messages as JSON payloads and provides SDK methods to fetch and manage them, but the visible inbox—the list, the badge, the read states—is yours to build. The docs say plainly that they're just getting started and are working on features to make this easier, so expect the build burden to shrink over time.
Can users filter Customer.io inbox messages by topic?
Yes. You assign topics to a message in the template when you compose it, and your inbox client filters by passing topics to the inbox() call—analytics.inbox('orders', 'billing') returns only messages tagged with those topics. One catch: a message with no topics matches every filter, so untagged messages appear in all filtered views.
How do I send an inbox message in Customer.io?
Drag an Inbox message block into a campaign or broadcast workflow, add content, and define the type, expiration and topics along with the JSON payload. Inbox messages can also be sent as transactional messages. Before any of this works, in-app messaging must be enabled in workspace settings and your site or app needs the SDK set up.
Can I personalise inbox messages with Liquid?
Yes. The JSON payload supports Liquid the same way email does. Use {{customer.first_name}} for profile attributes in any message, {{event.order_number}} for event data in event-triggered campaigns, and {{trigger.order_number}} for data passed when triggering a broadcast or transactional message. A Liquid rendering failure is the typical cause of a message reporting the failed metric.
What does the inbox message payload look like?
Every message carries five required fields—messageId, sentAt, expiresAt, opened, and a properties object that holds your content and cannot be empty—plus optional topics and type fields. By default properties contains a title and body, but you can structure it however your inbox client expects, adding fields like image or link.
How are inbox message opens tracked?
Your inbox client tracks them. When a user opens the inbox and sees a message, your code calls markOpened(), which reports both the delivered and opened metrics to Customer.io. There's no tracking pixel and no automatic open detection—if your client never calls the method, the message stays in Sent forever, regardless of whether the user saw it.
Why isn't my Customer.io inbox message showing?
Work through the short list from the docs: in-app and inbox messaging enabled in workspace settings, the in-app plugin loaded (analytics.inbox should be defined), and the user identified. Then check the message hasn't expired and that no topic filter excludes it—inbox('topic1') won't return a message tagged only topic2. Inbox messages follow the same delivery patterns as regular in-app messages, so the message also won't arrive until the user visits your site or app.
Should I use push or the notification inbox for important account messages?
Use both, for different jobs. Push interrupts: it's the right channel when the user should act now, but it's gone once dismissed. The inbox persists... the message waits until the user deals with it. For account-critical messages like payment failures or security alerts, the strong pattern is a push notification for urgency with an inbox message carrying the same content, plus email as the out-of-product record.
Do inbox messages work for anonymous visitors?
The docs' troubleshooting steps list an identified user as a requirement—if a message isn't appearing, one of the first checks is that the person is identified. Plan the inbox as a channel for known users, signed in to your product.
What metrics do Customer.io inbox messages report?
Five: sent (reported by Customer.io when the message goes out), delivered and opened (both reported when your client calls markOpened()), clicked (reported by trackClick()), and failed (reported by Customer.io, usually a Liquid rendering issue). The unusual one is delivered—it isn't independent of opened, because delivery for an inbox message has no meaning separate from visibility.
Sources
- Set up your notification inbox. Customer.io Docs, updated 8 June 2026.
- Notification inbox: messages your audience can revisit. Customer.io Release Notes, 19 February 2026.
- Send inbox messages. Customer.io Docs, updated 8 June 2026.
- Trigger inbox messages from your backend. Customer.io Docs, updated 8 June 2026.
- Inbox message metrics. Customer.io Docs, updated 8 June 2026.
- Notification inbox, JavaScript SDK. Customer.io Docs, updated 8 June 2026.
- Perceived versus Received: A Complex Nature of Notifications. ACM, ECCE '25 (36th Annual Conference of the European Association of Cognitive Ergonomics), 2025.
- Study: Average teen received more than 200 app notifications a day. Michigan Medicine, 26 September 2023.
- Gordon Matthews, "father of voice mail," dead at 65. Plainview Herald, 23 February 2002.
- An Outlook. Door County Pulse, 21 February 2008.


