Stop Interrupting Users With Modals: Build Inline In-App Messages in Customer.io Instead
Open an in-app message in Customer.io, click the message background, and you'll find four Display options: Modal, Overlay, Inline and Tooltip. Most teams never get past the first one. A modal is what "in-app message" means in most people's heads—a box appears, the screen dims, the user deals with it before they can carry on. For announcements that must be acknowledged, that's correct. For everything else... feature nudges, empty states, plan banners, contextual tips... it's an interruption you didn't need to make.
This post makes the case for the format most teams don't know exists: inline in-app messages, which render inside a placeholder element in your product, look like native UI, and update without an app release. You'll get a decision rule for choosing between the four formats, the three inline placements worth building first, and the setup gotchas that have cost real teams days of debugging.
The modal is the default, not the right answer
A modal spends user attention whether the message deserves it or not. Set an in-app message's Display to Modal and Customer.io renders it on top of a background overlay—black at 20% opacity by default—positioned at the top, centre or bottom of the screen (send in-app messages docs). One practitioner guide describes modals more bluntly: "full-screen or overlay popups that block interaction".
Blocking is sometimes the point. A consent change, a price rise, an incident notice... these should stop the user. But scroll through the campaigns in a typical workspace and the modals are carrying feature announcements, onboarding tips and upgrade prompts—messages that didn't need to stop anyone. Each one trains users to close your messages without reading them.
We've argued before that your first in-app message shouldn't be a welcome modal at all—the three behavioural triggers worth configuring first do more for activation. This post pushes the same logic further: for a whole class of messages, no modal at any point is the right call.
None of this argues against the channel. Airship's Mobile App Push Notification Benchmarks for 2025 cites earlier Airship research finding that apps using both push and in-app messaging with segmentation and automation saw an average 31% higher Engagement Score than their app category average. In-app works. The question is which format.
What an inline in-app message actually is
An inline message renders inside an empty placeholder element in your product rather than on top of it. Your developer adds the placeholder—an empty div on your website, a view in your mobile app—and gives it an Element ID. In Customer.io, you set the message's Display to Inline and enter the same ID. Customer.io fills the placeholder with your content, and when no message is queued the element stays empty.
Customer.io's iOS SDK docs describe the result: inline messages "can look and feel like a part of your app, but provide fresh and timely content without requiring app updates". Users don't perceive a message arriving. They see the product showing them something relevant.
Placeholder view, native look, no app release needed
The placeholder ships once; the content changes whenever you need it to. The Flutter SDK docs state it directly: "Inline messages let you show dynamic content without releasing a new version of your app." After the initial engineering work, marketing owns what appears in the slot—copy, audience, timing, expiry—from the same campaigns and segments that drive your email.
Two details define the format. First, the view manages its own height: it expands when a message loads and contracts when there isn't one, so nobody hard-codes dimensions. Second, there's no dismissal. The iOS SDK fires messageShown, errorWithMessage and messageActionTaken events but no messageDismissed—as the docs put it, inline messages "don't really have a concept of dismissal like modal messages do. They're meant to be a part of your app!"
That last point matters for strategy. Inline gives you a messaging surface that behaves like product UI but runs on the same data as the rest of your omnichannel messaging strategy—the segments, events and workflows already driving your email, SMS and push.
Modal vs overlay vs inline vs tooltip: the decision rule
Customer.io offers four display types, and the send docs list which platforms support each:
- Modal (iOS, Android, web). A box on a dimmed overlay at the top, centre or bottom of the screen. Only one modal displays at a time.
- Overlay (web only). Positioned relative to the browser window, up to six at once—one per position. Mobile apps won't display overlays.
- Inline (iOS, Android, web). Replaces an empty element in your UI.
- Tooltip (web only). Anchored to a specific page element via a CSS selector. Built for feature callouts and product tours.
The rule: pick the format by what you need from the user. A modal is for must-acknowledge moments, where the user shouldn't continue without responding. An overlay suits short-lived web announcements that shouldn't disturb the page layout. A tooltip is for messages about one specific control... "this button does something new". Inline is for everything that should live in the product: adoption nudges, empty states, plan and billing banners, contextual tips that stay visible until they're no longer true.
There's a second filter: attention. Every modal withdraws from a finite balance of user patience, the same budget that governs knowing when not to send. If a message doesn't justify stopping someone mid-task, it doesn't justify a modal. Inline lets you say the same thing without the toll.
Three inline placements worth building first
Each placement below is one empty element with an ID—a small, bounded request for your engineering team. Start with these three.
Empty states
An empty dashboard is the most under-used screen in your product. Before a user connects data or creates their first project, the screen shows a static illustration written once and never touched again. Put an inline placeholder there instead and the empty state becomes a live slot: new trialists see a setup prompt, users from a webinar cohort see a relevant template, and the copy changes weekly without a deploy. Once the user has data, the placement stays empty and the page behaves as normal.
Dashboard and feature-adoption nudges
A placeholder at the top of your dashboard turns feature adoption into an in-product job rather than an email-only one. The 3-email activation sprint gets people back into the product; an inline slot continues the same story once they arrive, triggered by the same events. And the day 30, 60 and 90 feature-adoption work gains a surface that doesn't depend on email opens—the nudge waits in the product until the user shows up.
Plan or billing banners
Usage limits, card expiry, renewal reminders, plan recommendations. Add a placeholder to your billing and settings pages, then scope the message with a page rule like Web contains /billing/* so it renders only where it's relevant. Liquid handles the personalisation—plan name, percentage of quota used, renewal date—with the same syntax as your emails. "You've used 80% of your events this month" is an interruption as a modal. On the billing page, it's information sitting exactly where the user would go looking for it.
What inline cannot do (and when a modal still wins)
Inline messages can be scrolled past. That's the design, and the limitation. When acknowledgement is non-negotiable—consent and legal changes, deprecations that break workflows, security incidents, warnings before destructive actions—use a modal. Blocking interaction is rude for a feature nudge and exactly right for a breaking change.
Three more constraints to plan around.
The engineering dependency is real. A placeholder must exist before any message can render in it. On mobile, the placeholder itself ships with an app release; only the content is release-free afterwards. A new placement is a sprint conversation, not a campaign setting.
Displayed messages hold their slot. If a message is already showing in an inline position, a higher-priority message won't replace it—it queues until the current one is dismissed or expires. The send docs call this out under message priority.
Platform edges exist. On iOS, InlineMessageUIView doesn't work inside a UITableView—the iOS docs list it as a known limitation (it works in UIStackView and UIScrollView). For React Native, a production write-up from December 2025 documents two hard-won rules. Initialise the SDK before any screen that renders an inline view mounts. And expect a layout bug that leaves blank space when no message matches—the fix is a small explicit height on the view.
Setup notes and gotchas
Keep element IDs globally unique, named by screen and position. The React Native guide above recommends <screen>_<position>—so dashboard_banner_1, billing_banner_1. An ID reused across screens fails silently on mobile, rendering in one place and nowhere else.
Page rules are case-sensitive. The send docs flag this directly: mobile rules must match the casing of the name in your screen events, and web rules should be lowercase.
Set expiration deliberately. In-app messages expire 30 days after sending by default, with a 60-day maximum. A quota warning tied to a billing period should expire with that period, not linger into the next one.
Measure with Tracked Names. In-app metrics move Sent → Opened → Clicked: a message counts as Opened the moment it displays, and only actions with a Tracked Name register as clicks, per the in-app metrics docs. Leave close buttons untracked or dismissals will inflate your click numbers. Inline has no dismissed metric at all, so clicks-to-opens is your engagement read.
Match the product's design, not your email's. An inline message only feels native if it uses the product's own colours, type and spacing. The brand-consistency discipline from our Design Studio guide applies here too, with the reference point flipped: consistency with the screen the message sits in, not with the templates in your inbox.
Test on a warm SDK. Use Send test, then restart the app or clear the cache before expecting your first message—the SDKs poll slowly until your workspace has sent something, a quirk the send docs call out.
Frequently asked questions
What is the difference between inline and modal in-app messages in Customer.io?
A modal renders on top of your UI on a dimmed background overlay and holds attention until the user deals with it; an inline message renders inside an empty placeholder element and behaves like part of the page. Both are built in the same editor and sent from the same campaigns—the difference is the Display setting and an Element ID. Use modal for must-acknowledge moments and inline for persistent, contextual content.
Can I show an in-app message without a pop-up?
Yes. Set Display to Inline and the message renders inside a placeholder element in your product instead of appearing as a pop-up. On web you can also use a tooltip, which anchors the message to a specific page element. Both need a small engineering setup first: an empty element with an ID for inline, a stable CSS selector for tooltips.
Do inline in-app messages need an app release to update?
No. Content, audience, timing and expiry are all controlled from Customer.io once the placeholder exists—the Flutter SDK docs state that inline messages "let you show dynamic content without releasing a new version of your app". The only change that needs a release is adding or moving the placeholder itself.
When should I use a modal instead of an inline message?
When the user must acknowledge the message before continuing: consent or legal changes, breaking changes to workflows they rely on, incident notices, and warnings before destructive actions. A modal blocks interaction, which is wrong for a nudge and right for a must-see. If being scrolled past is acceptable, the message belongs inline.
Do inline messages work on web as well as mobile apps?
Yes. Inline display is supported on iOS, Android and web—on a website you add an empty element for the message to replace; in a mobile app you add a view through the SDK. Overlay and tooltip displays, by contrast, are web-only.
How do I measure an inline in-app message?
The same way as any in-app message: Sent means the delivery was created, Opened means it displayed, and Clicked means someone used an action with a Tracked Name, per the in-app metrics docs. Clicks-to-opens is the ratio to watch for engagement. The SDKs also fire messageShown and messageActionTaken events if you want the data in your own analytics.
Why is there no dismissed metric for inline messages?
Because inline messages have no dismissal concept—they're designed to sit in the UI like any other component. The iOS SDK fires messageShown, errorWithMessage and messageActionTaken, but no messageDismissed. If a close control matters for your design, build it as an action—though Customer.io's metrics docs recommend leaving close buttons untracked so dismissals don't count as clicks.
What is an Element ID in Customer.io inline messages?
The identifier that links a message to a placeholder. Your developer sets it on the empty element or view; you enter the same ID in the message editor when Display is set to Inline. Keep IDs globally unique and prefixed by screen (dashboard_banner_1)—reusing one ID across screens causes silent failures on mobile.
What happens if I send two inline messages to the same Element ID?
Priority decides the queue order, but it won't evict a message that's already showing. A higher-priority message waits until the displayed one is dismissed or expires. If you regularly run several messages to one placement, set Priority explicitly on each so the queue order is deliberate.
How long does an in-app message wait before it expires?
30 days by default, configurable per message up to a 60-day maximum. You can set expiry as an absolute date, a relative window after sending, or a Liquid-derived value such as an event property. If the person never views the message before expiry, Customer.io cancels it and the delivery stays Sent, never Opened.
Can inline messages include buttons and custom actions?
Yes. Deep links open automatically, and custom actions pass an actionName and actionValue to your code through a callback or a global event listener—useful for triggering an upgrade flow or opening a settings screen from the message. Each SDK's inline docs include the setup.
Why isn't my inline message showing?
Check, in order: the Element ID in the message matches the placeholder exactly; the page rules match the page or screen, including casing; the person is identified; and the message hasn't expired. On mobile, two more: the SDK must initialise before the screen containing the placeholder mounts, and element IDs must not be reused across screens. On iOS, inline views inside a UITableView won't render—use a UIStackView or UIScrollView.
Can I personalise inline messages with Liquid?
Yes—the same Liquid as your emails: profile attributes, event properties, snippets. Page rules accept Liquid too, so you can scope a message dynamically with a rule like /{{event.product_family}}/*. That makes one inline campaign able to serve different segments with different content in the same placement.
Sources
- Inline in-app messages (iOS SDK). Customer.io Docs, updated 9 June 2026.
- Inline in-app messages (Flutter SDK). Customer.io Docs, updated 26 June 2025.
- Send in-app messages. Customer.io Docs, updated 8 June 2026.
- In-app metrics. Customer.io Docs, updated 8 June 2026.
- Integrating Customer.io Inline In-App Messages in React Native: A Production Guide. Shanavas Shaji, Medium, 30 December 2025.
- Mobile App Push Notification Benchmarks for 2025. Airship, 2025.


