Skip to main content
The Courier iOS SDK provides prebuilt UI components and APIs for building notification experiences in Swift. It handles authentication, token management, and real-time message delivery so you can focus on your app.
  • Inbox — prebuilt notification center with theming and custom rendering
  • Push Notifications — automatic token syncing and delivery tracking for APNS and FCM
  • Preferences — prebuilt UI for users to manage their notification settings
Available on GitHub.
RequirementValue
Min iOS version13.0
Package managersSwift Package Manager, CocoaPods

Installation

1. In Xcode, go to File > Add Packages
2. Paste: https://github.com/trycourier/courier-ios
3. Select the version and add to your target
If using CocoaPods, run pod install from your project’s ios/ directory after updating the Podfile.

Authentication

All SDK features (Inbox, Push, Preferences) require a signed-in user. Authentication is JWT-based; your backend generates a token and the SDK manages credentials across app sessions.
For a full walkthrough of JWT generation, see the Inbox Authentication guide.
1

Generate a JWT on your backend

Call the Issue Token endpoint from your server with the scopes your app needs:
curl -X POST https://api.courier.com/auth/issue-token \
  -H "Authorization: Bearer $YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "scope": "user_id:YOUR_USER_ID write:user-tokens inbox:read:messages inbox:write:events read:preferences write:preferences read:brands",
    "expires_in": "2 days"
  }'
2

Sign in the user

Pass the JWT to the SDK where you manage user state. Credentials persist across app sessions. If the token expires, generate a new one from your backend and call signIn again; the SDK does not handle token refresh automatically.
Task {
    await Courier.shared.signIn(
        userId: "your_user_id",
        accessToken: jwt
    )
}
3

Sign out when done

Task {
    await Courier.shared.signOut()
}
You can listen for authentication state changes:
let listener = await Courier.shared.addAuthenticationListener { userId in
    print(userId ?? "No user signed in")
}

listener.remove()

Inbox

Courier Inbox provides a prebuilt notification center UI for SwiftUI and UIKit. It supports theming, custom renderers, and real-time updates.
Inbox requires the Courier Inbox provider to be enabled in your workspace. If using JWT authentication, enable JWT support in the provider settings.JWT toggle in Courier provider settings
For an overview of how Courier Inbox works and how to send messages to it from your backend, see Get Started with Inbox and Send an Inbox Message.

Prebuilt UI

import Courier_iOS

CourierInboxView(
    didClickInboxMessageAtIndex: { message, index in
        message.isRead ? message.markAsUnread() : message.markAsRead()
    },
    didLongPressInboxMessageAtIndex: { message, index in
        message.markAsArchived()
    },
    didClickInboxActionForMessageAtIndex: { action, message, index in
        print(action, message, index)
    },
    didScrollInbox: { scrollView in
        print(scrollView.contentOffset.y)
    }
)

Theming

Pass a CourierInboxTheme to customize fonts, colors, unread indicators, swipe actions, and button styles. Both light and dark themes are supported.
CourierInboxView(
    canSwipePages: true,
    lightTheme: yourLightTheme,
    darkTheme: yourDarkTheme,
    ...
)
Default InboxStyled Inbox
You can also apply branding from Courier Studio. The SDK supports primary color and footer visibility from your brand settings.

Custom Inbox UI

For full control over rendering, use addInboxListener to receive raw message data and build your own UI:
let listener = await Courier.shared.addInboxListener(
    onLoading: { isRefresh in
        // Show loading state
    },
    onError: { error in
        // Handle error
    },
    onMessagesChanged: { messages, canPaginate, feed in
        // Update your custom UI with messages
    },
    onUnreadCountChanged: { count in
        // Update badge indicators
    },
    onTotalCountChanged: { count, feed in
        // Track total messages per feed
    },
    onPageAdded: { messages, canPaginate, isFirstPage, feed in
        // Efficiently append new pages during pagination
    },
    onMessageEvent: { message, index, feed, event in
        // React to individual events: .read, .unread, .archived, .opened, .added
    }
)

// Clean up
listener.remove()

Message Actions

try await Courier.shared.readMessage("messageId")
try await Courier.shared.unreadMessage("messageId")
try await Courier.shared.archiveMessage("messageId")
try await Courier.shared.readAllInboxMessages()

// Or via message object
message.markAsRead()
message.markAsUnread()
message.markAsArchived()

Push Notifications

The SDK simplifies push notification setup with automatic token syncing, delivery tracking, and permission management.
Push notifications require a physical device. Simulators do not reliably support push token registration or notification delivery.

Provider Setup

ProviderToken Syncing
APNSAutomatic (via CourierDelegate)
Firebase FCMManual
ExpoManual
OneSignalManual
Pusher BeamsManual
For step-by-step provider credential setup, see the APNS integration guide or FCM integration guide.

Automatic Token Syncing (APNS)

Extend CourierDelegate in your AppDelegate to automatically sync APNS tokens and handle notification events:
import Courier_iOS

@main
class AppDelegate: CourierDelegate {

    override func pushNotificationDeliveredInForeground(
        message: [AnyHashable: Any]
    ) -> UNNotificationPresentationOptions {
        return [.sound, .list, .banner, .badge]
    }

    override func pushNotificationClicked(message: [AnyHashable: Any]) {
        print("Notification clicked: \(message)")
    }
}

Manual Token Syncing

For FCM or other providers, sync tokens manually:
Task {
    // APNS token
    try await Courier.shared.setAPNSToken(deviceToken)

    // FCM or other provider
    try await Courier.shared.setToken(for: .firebaseFcm, token: fcmToken)
}

Notification Service Extension

To track notification delivery when the app is not running, add a Notification Service Extension:
1

Download the template

Download CourierNotificationServiceTemplate.zip and run sh make_template.sh. Watch the video walkthrough on GitHub for a step-by-step guide.
2

Add the extension target

In Xcode: File > New > Target > select “Courier Service” > Finish.
3

Link the Courier SDK

Add the Courier package to the new target via SPM or CocoaPods.

Requesting Permission

Prompt the user to allow notifications. iOS shows a system dialog the first time; if the user denies, they must enable notifications from device Settings.
let status = try await Courier.requestNotificationPermission()

Send a Test Notification

Once you’ve completed the setup above, send a test push using the Send API with push as the routing channel. See the APNS sending guide or FCM sending guide for complete examples.

Preferences

Courier Preferences provides a prebuilt UI for users to manage which notification topics and channels they subscribe to.
Topics and sections are configured in the Preferences Editor. See Preferences Overview for how preference enforcement works at send time.
import Courier_iOS

CourierPreferencesView(
    mode: .topic,
    onError: { error in
        print(error.localizedDescription)
    }
)

Preference Modes

  • Topic mode (.topic): shows subscription topics the user can toggle on or off
  • Channels mode (.channels([.push, .sms, .email])): shows per-channel controls for each topic

Theming

Pass a CourierPreferencesTheme to customize fonts, colors, toggle styles, and section headers. Light and dark themes are both supported, and Courier Studio branding is automatically applied when a brandId is provided.
Default PreferencesStyled Preferences

CourierClient

For advanced use cases, CourierClient provides direct access to the Courier API:
let client = CourierClient(
    jwt: "...",
    userId: "your_user_id"
)

// Token management
try await client.tokens.putUserToken(token: "...", provider: "apns")

// Inbox
let messages = try await client.inbox.getMessages(paginationLimit: 25)
let unreadCount = try await client.inbox.getUnreadMessageCount()
try await client.inbox.read(messageId: "...")
try await client.inbox.readAll()

// Inbox websocket (real-time updates across devices)
let socket = client.inbox.socket
socket.receivedMessage = { message in print(message) }
socket.receivedMessageEvent = { event in print(event) } // .read, .unread, .archive, .opened
try await socket.connect()
try await socket.sendSubscribe()

// Preferences
let prefs = try await client.preferences.getUserPreferences()
try await client.preferences.putUserPreferenceTopic(
    topicId: "...",
    status: .optedIn,
    hasCustomRouting: true,
    customRouting: [.push]
)

// Branding
let brand = try await client.brands.getBrand(brandId: "...")

// URL tracking (from push payloads or inbox messages)
try await client.tracking.postTrackingUrl(url: "courier_tracking_url", event: .delivered)
See the full API reference on GitHub.