In-app inbox & realtime
Push disappears; the inbox persists. Any send can also land in a per-user notification center that your app renders โ with live delivery over WebSocket while the app is open.
Deliver to the inbox
Add an inapp block to any send โ it's explicit opt-in, so plain pushes
never flood inboxes:
await nb.messages.send({
audience: { segment_id: "..." },
content: {
title: "Your report is ready ๐", // push
inapp: { // + persistent inbox copy
title: "Your report is ready ๐",
body: "Q3 numbers are in โ 12% up.",
url: "https://app.example/reports/q3"
}
}
});
Inbox-only (no push at all): send just the inapp block. Items appear in
delivery logs as channel inapp, personalized like any channel
({{first_name}} works).
Read it from your app
// list (device must be identified โ inbox is per-user)
const { items, unread } = await nb.inbox.list();
// items: [{ id, content: {title, body?, url?}, read_at, created_at }]
// badge handling
await nb.inbox.markRead(items.map(i => i.id));
// pagination
const older = await nb.inbox.list({ cursor: items.at(-1).created_at });
Live updates
const disconnect = nb.connectRealtime((frame) => {
if (frame.type === "inbox.new") addToUI(frame.payload); // {id, content, created_at}
if (frame.type === "inbox.read") syncBadge(frame.payload); // {ids} (another tab read them)
});
// auto-reconnects with backoff; call disconnect() on teardown
The wire protocol is generic {type, payload} frames โ presence and chat
topics will ride the same connection later, so your integration won't change.
Live in-app messages
Beyond the persistent inbox, a send can appear inside the running app the moment
you hit send โ a banner, modal, or toast. Add a live block:
"content": {
"title": "New feature ๐",
"live": { "title": "Hey {{first_name}} โ dark mode is here",
"body": "Try it from settings.", "display": "banner",
"url": "https://app.example.com/settings", "cta": "Turn it on" }
}
One SDK call renders it (dependency-free, XSS-safe โ content is text, never markup):
nb.enableLiveMessages(); // built-in banner/modal/toast UI
// or render yourself:
nb.enableLiveMessages({ onMessage: (m) => { myUI(m); return true; } });
Live messages are ephemeral by design: users online right now see them
instantly; everyone else is recorded as skipped ยท not_connected in the delivery
log โ never a silent success. Anything that must survive a closed tab belongs in
inapp (the inbox) โ and both blocks can ride the same send. CTA clicks and
dismissals are tracked automatically as live_click / live_dismiss
events, ready for segments.
Security model
| Who can read an inbox | Only the device aliased to that user โ and aliasing is HMAC-gated when an identify secret is set. An extracted client key alone cannot read anyone's inbox. |
| Raw endpoints | GET /v1/inbox?device_id=โฆ ยท
POST /v1/inbox/read ยท wss://api.notibase.com/v1/realtime?key=ck_โฆ&device_id=โฆ |