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 inboxOnly 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 endpointsGET /v1/inbox?device_id=โ€ฆ ยท POST /v1/inbox/read ยท wss://api.notibase.com/v1/realtime?key=ck_โ€ฆ&device_id=โ€ฆ
Replacing a homegrown notification center? The inbox schema is conversation-ready (items carry direction and an optional conversation id), so threads and two-way messages are an upgrade, not a migration.