---
title: "iOS custom inbox mobile"
slug: "ios-custom-inbox"
updated: 2026-06-09T22:54:06Z
published: 2026-06-09T22:54:06Z
canonical: "docs.xtremepush.com/ios-custom-inbox"
stale: true
---

> ## Documentation Index
> Fetch the complete documentation index at: https://docs.xtremepush.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 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.

**Swift**

```swift
XPush.inboxList(withOffset: 0, limit: 20) { (list, badge, error) in
    if let list = list {
        // render list
    } else if let error = error {
        // handle error
    }
}
```

**Objective-C**

```objectivec
[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:

| Property | Type | Description |
| --- | --- | --- |
| `identifier` | `NSInteger` | Unique message ID |
| `isOpened` | `BOOL` | Whether the message has been marked as opened |
| `isClicked` | `BOOL` | Whether the message has been marked as clicked |
| `isDelivered` | `BOOL` | Whether the message has been delivered |
| `createTimestamp` | `NSNumber *` | Unix timestamp of when the message was created |
| `expirationTimestamp` | `NSNumber *` | Unix timestamp of expiry, or nil if the message does not expire |
| `style` | `XPInboxItemStyle *` | Background and title background colour values |
| `isCard` | `BOOL` | `YES` for card layout (full-width banner image), `NO` for alert layout (small thumbnail) |
| `response` | `XPMessageResponse *` | 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:

| Property | Type | Set by |
| --- | --- | --- |
| `identifier` | `NSString *` | Platform (system-assigned) |
| `campaignIdentifier` | `NSString *` | Platform (system-assigned) |
| `title` | `NSString *` | Campaign — Push title field |
| `text` | `NSString *` | Campaign — Push text / body field |
| `icon` | `NSString *` | Campaign — Push icon field (falls back to app icon, then project icon, if not set) |
| `data` | `NSDictionary *` | Campaign — Custom payload fields (payload_add), with SDK-reserved keys removed |
| `payload` | `NSDictionary *` | Full raw message dictionary — use when you need direct access to fields not surfaced above |

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

| Property | Type | Set by |
| --- | --- | --- |
| `deeplink` | `NSString *` | Campaign — present when action type is **Deeplink**; `nil` otherwise |
| `url` | `NSURL *` | Campaign — present when action type is **URL**; `nil` otherwise |
| `identifier` | `NSString *` | 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:

| Property | Type | Set by |
| --- | --- | --- |
| `isCard` | `BOOL` | Campaign — inbox type setting. `YES` = card layout (full-width banner), `NO` = alert layout (small thumbnail) |
| `style.background` | `NSString *` | Campaign — inbox style background colour |
| `style.titleBackground` | `NSString *` | 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.

**Swift**

```swift
// Reading a custom payload field
if let ctaLabel = item.response.message.data?["cta_label"] as? String {
    button.setTitle(ctaLabel, for: .normal)
}
```

**Objective-C**

```objectivec
NSString *ctaLabel = item.response.message.data[@"cta_label"];
if (ctaLabel) {
    [button setTitle:ctaLabel forState:UIControlStateNormal];
}
```

> [!NOTE]
> 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.

**Swift**

```swift
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
    }
}
```

**Objective-C**

```objectivec
- (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.

**Swift**

```swift
XPush.reportMessageOpened(item.response.message, actionIdentifier: item.response.action.identifier)
```

**Objective-C**

```objectivec
[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.

**Swift**

```swift
XPush.reportMessageClicked(item.response.message, actionIdentifier: item.response.action.identifier)
```

**Objective-C**

```objectivec
[XPush reportMessageClicked:item.response.message actionIdentifier:item.response.action.identifier];
```

## Delete an inbox message

**Swift**

```swift
XPush.removeInboxMessage(item) { badge in
    // reload inbox list
    // update badge display
}
```

**Objective-C**

```objectivec
[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.

**Swift**

```swift
let badge: NSInteger = XPush.getInboxBadge()
```

**Objective-C**

```objectivec
NSInteger badge = [XPush getInboxBadge];
```
