iOS custom inbox mobile

Implement a custom inbox in your iOS Application

By default, the XtremePush SDK renders the inbox as a built-in WebView. A custom inbox replaces that with your own native UI, giving you full control over layout and interaction.

When you implement a custom inbox, the SDK continues to handle:

  • Fetching and paginating inbox messages from the platform
  • Deleting messages
  • Tracking badge counts
  • Sending opened/clicked analytics events (when you call the reporting methods)

Your app becomes responsible for:

  • Rendering each message (title, body, image, icon, card vs alert layout)
  • Handling tap actions (deeplink navigation, opening URLs)
  • Calling the reporting methods at the right time

Retrieve inbox messages

The inboxListWithOffset:limit:callback: method asynchronously fetches a list of inbox items. Use the offset and limit parameters to support pagination.

XPush.inboxList(withOffset: 0, limit: 20) { (list, badge, error) in
    if let list = list {
        // render list
    } else if let error = error {
        // handle error
    }
}
[XPush inboxListWithOffset:0 limit:20 callback:^(NSArray<XPInboxItem *> * _Nullable list, NSUInteger badge, NSError * _Nullable error) {
    if (list != nil) {
        // render list
    } else if (error != nil) {
        // handle error
    }
}];

XPInboxItem

Each item in the returned array is an XPInboxItem with the following properties:

PropertyTypeDescription
identifierNSIntegerUnique message ID
isOpenedBOOLWhether the message has been marked as opened
isClickedBOOLWhether the message has been marked as clicked
isDeliveredBOOLWhether the message has been delivered
createTimestampNSNumber *Unix timestamp of when the message was created
expirationTimestampNSNumber *Unix timestamp of expiry, or nil if the message does not expire
styleXPInboxItemStyle *Background and title background colour values
isCardBOOLYES for card layout (full-width banner image), NO for alert layout (small thumbnail)
responseXPMessageResponse *Contains the message content and tap action — see below

Working with message content

Each XPInboxItem exposes its content through item.response, which contains two objects:

item.response.message (XPMessage) — the message content:

PropertyTypeSet by
identifierNSString *Platform (system-assigned)
campaignIdentifierNSString *Platform (system-assigned)
titleNSString *Campaign — Push title field
textNSString *Campaign — Push text / body field
iconNSString *Campaign — Push icon field (falls back to app icon, then project icon, if not set)
dataNSDictionary *Campaign — Custom payload fields (payload_add), with SDK-reserved keys removed
payloadNSDictionary *Full raw message dictionary — use when you need direct access to fields not surfaced above

item.response.action (XPAction) — the tap action:

PropertyTypeSet by
deeplinkNSString *Campaign — present when action type is Deeplink; nil otherwise
urlNSURL *Campaign — present when action type is URL; nil otherwise
identifierNSString *Platform — pass to reporting methods

deeplink and url are mutually exclusive — only one will be set depending on the action type configured in the campaign. If neither is set, the message has no tap action.

Layout and style are on the item:

PropertyTypeSet by
isCardBOOLCampaign — inbox type setting. YES = card layout (full-width banner), NO = alert layout (small thumbnail)
style.backgroundNSString *Campaign — inbox style background colour
style.titleBackgroundNSString *Campaign — inbox style title bar colour

Custom payload fields

If your inbox UI requires data beyond the standard fields — for example a CTA label, a secondary URL, or a badge value — add custom fields in the campaign's Custom payload (payload_add) section. These are delivered in item.response.message.data as a key-value dictionary, with all SDK-reserved keys already stripped out.

// Reading a custom payload field
if let ctaLabel = item.response.message.data?["cta_label"] as? String {
    button.setTitle(ctaLabel, for: .normal)
}
NSString *ctaLabel = item.response.message.data[@"cta_label"];
if (ctaLabel) {
    [button setTitle:ctaLabel forState:UIControlStateNormal];
}
📘

Campaign Templates

Because your custom inbox UI is built to render a specific content structure, consider defining a reusable inbox campaign template in the Xtremepush platform that consistently populates the fields your UI expects. For example, if your card layout always shows a title, body, background colour, deeplink action, and a custom CTA label, define these as required fields for any inbox campaign targeting your custom implementation.

Handle message actions (deeplink & URL)

When a user taps a message, inspect item.response.action to determine what to do. The SDK does not fire deeplink callbacks automatically in a custom inbox — your app handles navigation directly.

Always call reportMessageClicked so that the interaction is registered on the platform.

func userDidTap(_ item: XPInboxItem) {

    // Register the click with the platform
    XPush.reportMessageClicked(item.response.message, actionIdentifier: item.response.action.identifier)

    if let deeplink = item.response.action.deeplink {
        // Route through your app's navigation handler
        // MyRouter.shared.navigate(to: deeplink)

    } else if let urlString = item.response.action.url,
              let url = URL(string: urlString) {
        UIApplication.shared.open(url)

    } else {
        // No tap action configured — message is informational only
    }
}
- (void)userDidTapInboxItem:(XPInboxItem *)item {

    // Register the click with the platform
    [XPush reportMessageClicked:item.response.message actionIdentifier:item.response.action.identifier];

    if (item.response.action.deeplink) {
        // Route through your app's navigation handler
        // [[MyRouter shared] navigateTo:item.response.action.deeplink];

    } else if (item.response.action.url) {
        NSURL *url = [NSURL URLWithString:item.response.action.url];
        [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];

    } else {
        // No tap action configured — message is informational only
    }
}

Report inbox message opened

Call this when a message becomes visible to the user (e.g. scrolled into view in a feed) without a deliberate tap.

XPush.reportMessageOpened(item.response.message, actionIdentifier: item.response.action.identifier)
[XPush reportMessageOpened:item.response.message actionIdentifier:item.response.action.identifier];

Report inbox message clicked

Call this when a user deliberately taps a message. This automatically also marks the message as opened. Do not call reportMessageOpened separately for tapped messages.

XPush.reportMessageClicked(item.response.message, actionIdentifier: item.response.action.identifier)
[XPush reportMessageClicked:item.response.message actionIdentifier:item.response.action.identifier];

Delete an inbox message

XPush.removeInboxMessage(item) { badge in
    // reload inbox list
    // update badge display
}
[XPush removeInboxMessage:item callback:^(NSInteger badge) {
    // reload inbox list
    // update badge display
}];

Get inbox badge number

Returns the cached unread count — use this to display a badge without making a network request.

let badge: NSInteger = XPush.getInboxBadge()
NSInteger badge = [XPush getInboxBadge];