Push Notifications Vanish. Customer.io's Notification Inbox Is for the Messages That Shouldn't

By·Published·Updated
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.

TL;DR: Push and in-app messages are deleted the moment they're dismissed—the wrong carrier for a billing warning, a security notice or an order update. The notification inbox holds those until the user deals with them. Since 23 June 2026 you can set one up without a developer, and the docs now describe it rendering on the web and in native mobile apps. The JSON build-your-own route survives as the advanced path, still the only option for arbitrary structured data. One inbox per workspace, messages expiring no more than 60 days out, and an "opened" metric that means whatever your client reports. The four-question rule below sorts any message class into a channel.

This post covers what the inbox actually is: on the build-your-own path, JSON payloads rather than 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 of that path, which Customer.io has since cut for the standard case.

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

Take the build-your-own path and the notification inbox becomes 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 if all you need is persistence 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 advanced inbox 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:

  1. Will the user need this message again after first seeing it? If yes, the inbox carries it—whatever else you also send.
  2. 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. Since 22 July 2026 you can add the inbox copy from the push itself, provided the inbox is enabled in your workspace, so double-tracking is a setting rather than two builds.
  3. 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.
  4. 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 Email
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

Update (August 2026): this section described the only route that existed when we published. On 23 June 2026 Customer.io shipped a no-code inbox you create in workspace settings and style in Design Studio, and the docs now describe it rendering on the web and in native mobile apps. Everything below is still accurate for the build-your-own path, which the docs have moved to their advanced section. It stays the right choice for arbitrary JSON, for rendering inside your own design system, and for targeting more than your workspace's default app. If all you need is persistence, start with the visual inbox—we've written up that decision in the no-code inbox changes the build decision.

When the inbox launched, the docs didn't oversell it, and neither should you when pitching a custom build internally. The setup page led 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." That warning has since gone from the page, which tells you how fast this moved.

Translated, for the build-your-own path: there is no drop-in widget. Marketing cannot switch a custom inbox on alone—engineering builds the 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.

Platform Minimum for a build-your-own inbox
cdp-analytics-browser (web package import) 0.3.11
iOS 4.2
Android 4.16
React Native 6.2
Flutter 3.3
Expo 3.1

The visual inbox asks for more on the web: cdp-analytics-browser 0.4.16 or later. A site already running a custom inbox is not automatically ready for the visual one.

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 and opens the inbox. Stranger still: on a custom inbox, delivered is reported by your markOpened() call, together with opened. As the docs put it, "an inbox message isn't 'delivered' independently of being seen (as it might be for email)".

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, on a custom inbox "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 stay available until they expire or the user deletes them, and you can either use Customer.io's own visual inbox or render the JSON payloads in an inbox you build yourself. 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 persists until it expires or the user deletes it, it bypasses in-app prioritisation entirely, and it renders either in Customer.io's visual inbox or in one you build from the JSON payload. They also report metrics differently: on a custom inbox, 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 60 days after the send is the maximum. The docs call 60 days the default and, elsewhere on the same page, give an unset expiration 30 days—so set the value explicitly rather than trusting either. Once a message expires it disappears from the inbox.

Which Customer.io SDK versions support the notification inbox?

For a build-your-own 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. The visual inbox needs cdp-analytics-browser 0.4.16 or later. 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?

Not since 23 June 2026. Customer.io ships a visual inbox you create in workspace settings, style in Design Studio and publish, with no front-end code, provided the JavaScript SDK is already on your site. The docs now describe it rendering on the web and in native mobile apps. You still build your own for arbitrary JSON payloads, for rendering inside your own design system, or to target more than your workspace's default app.

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?

Add an inbox message to a campaign, broadcast or transactional message, then pick an editor: the visual editor for Customer.io's visual inbox, the advanced editor to compose a JSON payload for an inbox you built. Set the type, expiration and topics alongside the content. 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?

On a build-your-own inbox, your 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. Since 22 July 2026 you can add that inbox copy from the push itself, so the pattern is one setting rather than two builds.

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 a custom inbox 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

Want this working in your Customer.io workspace?

It's what we do all day for SMBs—strategy, automations, deliverability and hands-on execution.

See how we work as a Customer.io agency →
David Crowther
Book a free consultation →

On our call, you'll be speaking with David Crowther, founder of NerveCentral.

Our initial consultation is not a sales call—you'll talk, I'll listen and ask questions—then I'll come back to you within 48 hours with our best ideas on how to grow your business.