This guide focuses on implementing the Inbox channel using the Mobile SDK. Before starting the integration, make sure the Inbox channel is configured in Dengage, including application setup.
For an overview, see the Inbox User Guide.
Using the Inbox Channel (/api/inbox/*) in dengage-ios-sdk and dengage-android-sdk. The SDK is headless: it fetches messages and reports events. The list, cells, empty state and deeplink routing are yours.
This is not the legacy Inbox (getInboxMessages/InboxMessage), which remains a separate API for backwards compatibility. Both are gated by the sameinboxEnabledflag but are independent surfaces — don't use both against the same messages in one screen.
| Purpose | iOS | Android | Endpoint |
|---|---|---|---|
| Fetch messages | Dengage.getInboxChannelMessages(limit:completion:) | Dengage.getInboxChannelMessages(limit, dengageCallback) | GET /api/inbox/getMessages |
| Report events | Dengage.sendInboxChannelEvents(_:completion:) | Dengage.sendInboxChannelEvents(events) | POST /api/inbox/events |
1. Prerequisites
The calls are silent no-ops unless all of these hold: the SDK has been started and a subscription exists, the remote SDK params contain an accountName, and inboxEnabled is true (a server-side account setting). When the guard fails, iOS completes with .success([]) and Android calls onResult(mutableListOf()) — so an empty list is not necessarily an error; check the account's inboxEnabled before debugging the client.
iOS gotcha: the call goes through an optional chain (dengage?…). If the SDK was never started the completion is never invoked — not even with an empty result. Don't hang a loading spinner on the completion alone.
2. Data model
Same shape on both platforms, only the naming differs (DengageInboxChannelMessage / InboxChannelMessage):
| Field | JSON | Notes |
|---|---|---|
id | smsgId | The id used in every event |
isRead | isRead | Merged with the local cache on fetch |
priority | priority | Defaults to 0 |
data | messageJson | Payload |
isDeleted | — | Local only, never sent by the server |
Payload (data): title, message, imageUrl, ctaButtons, isPinned, receiveDate (JSON key receiveDateUTC, format yyyy-MM-dd'T'HH:mm:ss.SSS'Z') and messageDetails.
CTA button: buttonId, label, iosDeeplink, androidDeeplink, webUrl — all optional. Prefer the platform deeplink, fall back to webUrl.
messageDetailsis mandatory. It is an opaque server token; every event reported for a message must echo it back verbatim or the backend cannot attribute the interaction. Build events from the message object, not from a bare id.
3. Fetching
limit accepts 1–100, default 20. Messages arrive already merged with the SDK's locally stored read/deleted state, with locally deleted ones filtered out.
SWIFT
// iOS — the completion runs on a BACKGROUND (URLSession) thread; hop to main for UI
Dengage.getInboxChannelMessages(limit: 20) { [weak self] result in
switch result {
case .success(let messages):
DispatchQueue.main.async { self?.render(messages) }
self?.sendImpressions(for: messages)
case .failure(let error):
print("getInboxChannelMessages failed: \(error)")
}
}KOTLIN
// Android — callbacks already arrive on MAIN (work runs on IO, delivery on Main)
Dengage.getInboxChannelMessages(
limit = 20,
dengageCallback = object : DengageCallback<MutableList<InboxChannelMessage>> {
override fun onResult(result: MutableList<InboxChannelMessage>) {
adapter.setItems(result)
sendImpressions(result)
}
override fun onError(error: DengageError) { /* ... */ }
}
)4. Reporting events
| Meaning | Code | iOS | Android | When |
|---|---|---|---|---|
| Impression | IM | .impression | IMPRESSION | The message became visible in the list |
| Open | OP | .open | OPEN | The user opened / read it |
| Click | CL | .click | CLICK | The message or a CTA was tapped |
| Delete | DT | .delete | DELETE | The user deleted it |
The request is bulk: one call carries many events — which is what makes "report an impression for every message just rendered" one request instead of N. The SDK stamps eventDateUTC itself (UTC now).
SWIFT
// iOS
let event = DengageInboxChannelEvent(eventType: .click,
messageId: message.id,
messageDetails: message.data.messageDetails)
Dengage.sendInboxChannelEvents([event]) { _ in }KOTLIN
// Android — no result callback; failures only reach DengageLogger
val event = InboxChannelEvent(InboxChannelEventType.CLICK, message.id, message.data.messageDetails)
Dengage.sendInboxChannelEvents(listOf(event))5. Typical flow
As implemented by the example apps (InboxChannelViewController.swift, InboxChannelFragment.kt):
- Screen opens →
getInboxChannelMessages(limit: 20) - List renders → one bulk
IMfor every visible message - Row tapped →
CL+ setisRead = truelocally + open the deeplink - Open/read action →
OP+ setisRead = truelocally - Delete →
DT+ remove the row immediately