Inbox - Mobile SDK

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 same inboxEnabled flag but are independent surfaces — don't use both against the same messages in one screen.

PurposeiOSAndroidEndpoint
Fetch messagesDengage.getInboxChannelMessages(limit:completion:)Dengage.getInboxChannelMessages(limit, dengageCallback)GET /api/inbox/getMessages
Report eventsDengage.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):

FieldJSONNotes
idsmsgIdThe id used in every event
isReadisReadMerged with the local cache on fetch
prioritypriorityDefaults to 0
datamessageJsonPayload
isDeletedLocal 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.

ℹ️

messageDetails is 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

MeaningCodeiOSAndroidWhen
ImpressionIM.impressionIMPRESSIONThe message became visible in the list
OpenOP.openOPENThe user opened / read it
ClickCL.clickCLICKThe message or a CTA was tapped
DeleteDT.deleteDELETEThe 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):

  1. Screen opens → getInboxChannelMessages(limit: 20)
  2. List renders → one bulk IM for every visible message
  3. Row tapped → CL + set isRead = true locally + open the deeplink
  4. Open/read action → OP + set isRead = true locally
  5. Delete → DT + remove the row immediately