In-app messages

A message shown inside your app rather than sent to it. You publish a rule in the console; the SDK fetches it, caches it, and decides on the device when to show it — on app open, or when your app puts a value in front of it.

Not the same thing as the in-app inbox. The inbox is a list that waits for somebody to open it. An in-app message covers the screen at a moment you chose. They share a name and nothing else.

Why the device decides

The moment worth acting on is the app opening, and a server is not there when it happens. So the split is:

Resolved on our sideResolved on the device
Status, the display window, and the segment — every check that needs data a device must not have, or that a device could get wrong. A phone with a wrong clock cannot extend a campaign past its end date, and the segment filter never leaves the API. The trigger, and how many times this person has already seen it. An app that opens with no signal still has to know it showed this yesterday, and asking us on every cold start would put a network call in front of every launch.

Counting on the device has a cost worth knowing: a reinstall forgets, and two phones belonging to one person count separately. Both err towards showing a message again, which a person can dismiss.

Turn it on

// web — @notibase/web
nb.enableInAppMessages();

// Android — Kotlin
Notibase.enableInAppMessages()

// iOS — Swift
Notibase.enableInAppMessages()

// Flutter — Dart
await Notibase.enableInAppMessages(
  navigatorKey: navigatorKey,
  onPromptPush: () => FirebaseMessaging.instance.requestPermission(),
);

That is the whole integration for messages that fire on app open. Everything else is authored in the console under Delivery → In-app messages.

Triggers your app sets

A trigger is a local fact, not an event that travels to us. Set one and any rule keyed on it is evaluated there and then:

nb.setTrigger("cart_value", 240);          // web
Notibase.setTrigger("cart_value", 240)     // Android · iOS
Notibase.setTrigger('cart_value', 240);    // Flutter

A campaign configured for cart_value is over 100 fires on the next evaluation. Values are compared without coercing across types: the string "240" does not satisfy over 100. That is deliberate — a value that sometimes arrives as a string should show up as a message that did not fire, not one that fired for the wrong people.

The other two trigger kinds need nothing from your code: every app open, and on the nth session, counted by the SDK.

Priming the push permission prompt

This is the reason to reach for in-app messages first.

iOS and Android each give an app one system permission dialog per install, and a decline is close to permanent — the only way back is Settings, which nobody visits. An in-app message with an Ask for push permission button lets you ask inside your own UI first, where a “not now” costs you nothing and you can ask again next month.

The SDK only calls the system API after the person presses that button. There is no way to do this from a server, because the server does not know when the app is open.

What a button can do

ActionWhat happens
Just close itDismisses the message.
Open a linkOpens the URL, in a new tab on web.
Ask for push permissionCalls the platform's own prompt — see above.
Tag the personSets an attribute you chose, on the person behind that device. The value comes from the campaign, never from the device, so a publishable key cannot be used to write arbitrary attributes. The tag is filterable in segments as soon as it lands.
Record a pressTracks a named event, so you can tell two buttons apart.

Every press closes the message. A message left standing behind a permission dialog is one the person has to dismiss twice, and nobody reads it the second time.

Content is blocks, not HTML

A message is a small document of text, image, button and spacer blocks, rendered natively on every platform. That means no WebView on mobile and no innerHTML on the web, so message copy can never become code running inside your app. It also means the console's preview is the same renderer your users get, rather than an approximation.

A block type an older SDK does not recognise is skipped rather than guessed, and a trigger kind it does not recognise does not fire — showing it would mean ignoring a condition you deliberately set.

Impressions and CTR

The SDK reports a display, a press and a dismissal; the console shows impressions and click-through rate per message. A display is counted before the message is drawn, because somebody who closes the app the instant it appears has still seen it. Counting stops when a message is paused, so a rate you are reading does not move after you stopped the campaign — devices keep a cached copy for a while and go on reporting.

Platforms

PlatformStatus
Web (@notibase/web)Available
Android (Kotlin)Available
iOS (Swift)Available
FlutterAvailable — pass your navigatorKey, and an onPromptPush callback if you want the permission button

A rule reaches only the platforms whose SDK can evaluate it; nothing is queued for the others, so a device running an older SDK is simply not shown in-app messages rather than shown them late.

Flutter needs two things the other SDKs do not, and both are consequences of the package having no platform channels: it is handed your navigator, because a message is pushed as a route through your own app; and it is handed a callback for the push permission prompt, because asking belongs to firebase_messaging and taking a dependency on it to make one call would break the promise the package is built on.

Endpoints

CallKeyWhat
GET /v1/in-app?device_id=…ckThe rules this device may act on, already filtered to live, in-window and in-segment. Never includes the segment itself.
POST /v1/in-app/eventck{ device_id, id, event, tag? } where event is shown, clicked or dismissed. tag is the key of a pressed tag button — the key only.
← In-app inboxEmail →