Skip to main content

Documentation Index

Fetch the complete documentation index at: https://www.courier.com/docs/llms.txt

Use this file to discover all available pages before exploring further.

The Courier Android SDK provides prebuilt UI components and APIs for building notification experiences in Kotlin. It handles authentication, token management, and real-time message delivery so you can focus on your app.
  • Inbox — prebuilt notification center for Jetpack Compose and XML layouts
  • Push Notifications — automatic FCM token syncing and delivery tracking
  • Preferences — prebuilt UI for users to manage their notification settings
Available on GitHub.
RequirementValue
Min Android SDK23
Gradle8.4+
Package managerJitpack

Installation

1

Add the Jitpack repository

In your settings.gradle or settings.gradle.kts:
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven { url 'https://jitpack.io' }
    }
}
2

Add the dependency

In your app’s build.gradle:
dependencies {
    implementation 'com.github.trycourier:courier-android:LATEST_VERSION'
}
Replace LATEST_VERSION with the current version from GitHub Releases.
3

Initialize the SDK

Call Courier.initialize() in your Application class before using other SDK features. This gives Courier access to SharedPreferences for persisting state across sessions.
class YourApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        Courier.initialize(this)
    }
}
If you only plan to use CourierClient APIs directly, you can skip the initialize step.

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:
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.
lifecycleScope.launch {
    Courier.shared.signIn(
        accessToken = jwt,
        userId = "your_user_id"
    )
}
3

Sign out when done

lifecycleScope.launch {
    Courier.shared.signOut()
}

EU-hosted workspaces

For EU-hosted Courier workspaces, pass the built-in EU preset through apiUrls:
lifecycleScope.launch {
    Courier.shared.signIn(
        accessToken = jwt,
        userId = "your_user_id",
        apiUrls = CourierClient.ApiUrls.eu()
    )
}

Authentication state

val userId = Courier.shared.userId
val isSignedIn = Courier.shared.isUserSignedIn

val listener = Courier.shared.addAuthenticationListener { userId ->
    print(userId ?: "No user signed in")
}

listener.remove()

Inbox

Courier Inbox provides a prebuilt notification center UI for Jetpack Compose and XML layouts. 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
Your app theme must extend Theme.MaterialComponents for the prebuilt UI to render correctly. Set this in your res/values/themes.xml.
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

CourierInbox(
    modifier = Modifier.padding(innerPadding),
    onClickMessageListener = { message, index ->
        if (message.isRead) message.markAsUnread() else message.markAsRead()
    },
    onLongPressMessageListener = { message, index ->
        message.markAsArchived()
    },
    onClickActionListener = { action, message, index ->
        print(message.toString())
    },
    onScrollInboxListener = { offsetInDp ->
        print(offsetInDp.toString())
    }
)

Theming

Pass a CourierInboxTheme to customize fonts, colors, unread indicators, swipe actions, and button styles. Both light and dark themes are supported.
CourierInbox(
    modifier = Modifier.padding(innerPadding),
    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:
lifecycleScope.launch {
    val listener = Courier.shared.addInboxListener(
        onLoading = {
            // Show loading state
        },
        onError = { error ->
            // Handle error
        },
        onMessagesChanged = { messages, canPaginate, feed ->
            // Update your custom UI with messages
        },
        onUnreadCountChanged = { count ->
            // Update badge indicators
        },
        onTotalCountChanged = { count, feed ->
            // Track total messages per feed
        },
        onPageAdded = { messages, canPaginate, isFirstPage, feed ->
            // Efficiently append new pages during pagination
        },
        onMessageEvent = { message, index, feed, event ->
            // React to individual events: READ, UNREAD, ARCHIVED, OPENED, ADDED
        }
    )

    // Clean up when done
    listener.remove()
}

Message Actions

lifecycleScope.launch {
    Courier.shared.readMessage(messageId = "...")
    Courier.shared.unreadMessage(messageId = "...")
    Courier.shared.openMessage(messageId = "...")
    Courier.shared.clickMessage(messageId = "...")
    Courier.shared.archiveMessage(messageId = "...")
    Courier.shared.readAllInboxMessages()
}

// Or via the message object
message.markAsRead()
message.markAsUnread()
message.markAsOpened()
message.markAsClicked()
message.markAsArchived()

// Track an action click on an inbox message
message.actions?.firstOrNull()?.markAsClicked()

Reading the feed

Inspect the loaded inbox without subscribing to a listener:
val feedMessages = Courier.shared.feedMessages
val archivedMessages = Courier.shared.archivedMessages

lifecycleScope.launch {
    // Pagination is automatic in the prebuilt UI; you can also drive it manually:
    Courier.shared.fetchNextInboxPage(InboxMessageFeed.FEED)

    // Pull-to-refresh
    Courier.shared.refreshInbox()
}

// Adjust how many messages are loaded per page (default: 32)
Courier.shared.inboxPaginationLimit = 50

Push Notifications

The SDK simplifies push notification setup with automatic FCM token syncing, delivery tracking, and permission management.
Firebase is now a separate dependency. Starting with Courier Android 6.x, the SDK no longer bundles Firebase Messaging as a transitive dependency. Your app must add its own Firebase BoM and firebase-messaging artifact so you can subclass FirebaseMessagingService.
Push notifications require a physical device and a release build for reliable token registration and delivery. Emulators are best-effort.

Provider Setup

ProviderToken Syncing
Firebase FCMAutomatic (via FirebaseMessagingService)
ExpoManual
OneSignalManual
Pusher BeamsManual
For step-by-step provider credential setup, see the FCM integration guide. Initialize the Firebase SDK in your project before continuing.

Add Firebase to your app

In your app’s build.gradle, declare Firebase Messaging alongside the Courier SDK:
dependencies {
    // Required: your app must declare Firebase explicitly
    implementation platform('com.google.firebase:firebase-bom:33.1.2')
    implementation "com.google.firebase:firebase-messaging"
}
Then complete the Firebase Android setup (download google-services.json, apply the Google Services plugin, etc.).

Automatic Token Syncing (FCM)

1

Create a FirebaseMessagingService

Subclass Firebase’s FirebaseMessagingService directly and forward both callbacks to Courier. The SDK caches the token, uploads it to Courier when a user is signed in, and broadcasts delivery events through its event bus.
package your.app.package

import com.courier.android.Courier
import com.courier.android.notifications.CourierPushNotificationIntent
import com.courier.android.notifications.presentNotification
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage

class CourierPushNotificationService : FirebaseMessagingService() {

    override fun onMessageReceived(message: RemoteMessage) {
        super.onMessageReceived(message)

        // Tell the Courier SDK that a push was delivered
        Courier.onMessageReceived(message.data)

        // Build the PendingIntent that opens your Activity and carries the original payload
        val notificationIntent = CourierPushNotificationIntent(
            context = this,
            target = MainActivity::class.java,
            payload = message
        )

        // Show the notification. Prefer data-only FCM so this service runs even when the app is killed.
        notificationIntent.presentNotification(
            title = message.data["title"] ?: message.notification?.title,
            body = message.data["body"] ?: message.notification?.body
        )
    }

    override fun onNewToken(token: String) {
        super.onNewToken(token)

        // Register/refresh the FCM token with Courier and link it to the current user
        Courier.onNewToken(token)
    }
}
2

Register the service in AndroidManifest.xml

<manifest>
    <application>
        <service
            android:name=".CourierPushNotificationService"
            android:exported="false">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>
    </application>
</manifest>
3

Handle clicks with CourierActivity

Extend CourierActivity (typically your MainActivity) to receive delivery and click events while your app is foregrounded.
class MainActivity : CourierActivity() {
    override fun onPushNotificationDelivered(pushNotification: Map<String, String>) {
        // Handle notification delivery while in foreground
    }

    override fun onPushNotificationClicked(pushNotification: Map<String, String>) {
        // Handle notification click
    }
}

Reading and refreshing the FCM token

The SDK caches the FCM token locally after Firebase delivers it. You can read or force-refresh it at any time:
val fcmToken = Courier.shared.fcmToken

lifecycleScope.launch {
    Courier.shared.refreshFcmToken()
    val updated = Courier.shared.fcmToken
    print(updated)
}

Non-FCM Providers

For Expo, OneSignal, Pusher Beams, or any other provider, sync tokens by string key:
lifecycleScope.launch {
    // Save a token. If the user is signed in it uploads immediately;
    // otherwise it is stored locally and uploaded on sign-in.
    Courier.shared.setToken(
        provider = "your-provider-key",
        token = "your_messaging_token"
    )

    val token = Courier.shared.getToken(provider = "your-provider-key")
}

Manual Notification Tracking

If you are not using the FirebaseMessagingService + CourierActivity setup above, you can post tracking events yourself:
lifecycleScope.launch {
    val client = Courier.shared.client // or CourierClient(...)

    client.tracking.postTrackingUrl(
        url = "https://courier.com/your_tracking_url",
        event = CourierTrackingEvent.CLICKED
    )
}

Requesting Permission

Android 13+ (API 33) requires a runtime permission. The call is safe to make on older versions too.
Courier.shared.requestNotificationPermission(activity)

val isGranted = Courier.shared.isPushPermissionGranted(context)

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 FCM sending guide for a complete example.

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.
CourierPreferences(
    mode = CourierPreferences.Mode.Topic,
    onError = { error ->
        print(error.toString())
    }
)

Preference Modes

  • Topic mode (Mode.Topic): shows subscription topics the user can toggle on or off
  • Channels mode (Mode.Channels(listOf(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 is a low-level wrapper around the Courier API. Each client holds its own credentials, so you can spin up as many as you need.

Initialization

val client = CourierClient(
    jwt          = "...",          // Optional. Required for most authenticated calls
    clientKey    = "...",          // Optional. Used only for Inbox client-key auth
    userId       = "your_user_id",
    connectionId = "...",          // Optional. Used for the inbox websocket
    tenantId     = "...",          // Optional. Scopes the client to a tenant
    apiUrls      = CourierClient.ApiUrls.eu(), // Optional. Use for EU-hosted workspaces
    showLogs     = true            // Optional. Defaults to your build configuration
)

val options = client.options
client.log("...")
client.warn("...")
client.error("...")

Token Management

client.tokens.putUserToken(
    token = "...",
    provider = "firebase-fcm"
)

// Customize device metadata stored alongside the token
val device = CourierDevice(
    appId        = "APP_ID",
    adId         = "AD_ID",
    deviceId     = "DEVICE_ID",
    platform     = "android",
    manufacturer = "Google",
    model        = "Pixel 8"
)

// Or use what the SDK can detect about the current device
// CourierDevice.current(context)

client.tokens.putUserToken(
    token = "...",
    provider = "firebase-fcm",
    device = device
)

client.tokens.deleteUserToken(token = "...")

Inbox

val messages = client.inbox.getMessages(
    paginationLimit = 25,
    startCursor = null
)

val archived = client.inbox.getArchivedMessages(
    paginationLimit = 25,
    startCursor = null
)

val unreadCount = client.inbox.getUnreadMessageCount()

client.inbox.open(messageId = "...")
client.inbox.read(messageId = "...")
client.inbox.unread(messageId = "...")
client.inbox.archive(messageId = "...")
client.inbox.readAll()

Inbox websocket

Receive real-time updates across devices:
val socket = client.inbox.socket

socket.onOpen  = { print("Socket opened") }
socket.onClose = { code, reason -> print("Socket closed: $code, $reason") }
socket.onError = { error -> print(error) }

socket.receivedMessage = { message -> print(message) }
// Fires only when the event comes from a different connectionId
// Available events: READ, UNREAD, MARK_ALL_READ, OPENED, ARCHIVE
socket.receivedMessageEvent = { event -> print(event) }

lifecycleScope.launch {
    socket.connect()
    socket.sendSubscribe()

    // When done:
    socket.disconnect()
}

Preferences

val prefs = client.preferences.getUserPreferences(paginationCursor = null)

val topic = client.preferences.getUserPreferenceTopic(topicId = "...")

client.preferences.putUserPreferenceTopic(
    topicId = "...",
    status = CourierPreferenceStatus.OPTED_IN,
    hasCustomRouting = true,
    customRouting = listOf(CourierPreferenceChannel.PUSH)
)

Branding

val brand = client.brands.getBrand(brandId = "...")

URL Tracking

Pass any tracking URL found inside a push notification payload or inbox message:
// Available events: CLICKED, DELIVERED, OPENED, READ, UNREAD
client.tracking.postTrackingUrl(
    url = "courier_tracking_url",
    event = CourierTrackingEvent.DELIVERED
)
See the full Courier API reference at courier.com/docs/reference.

Inbox Overview

Learn about Courier Inbox and how to set it up

Push Integrations

Configure FCM and other push providers

Preferences

Set up notification preference topics and channels

GitHub

Source code, examples, and changelog