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

# Courier Android SDK

> Add in-app notifications, push notifications, and notification preferences to your Android app with prebuilt UI components and Kotlin APIs.

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](#inbox)** — prebuilt notification center for Jetpack Compose and XML layouts
* **[Push Notifications](#push-notifications)** — automatic FCM token syncing and delivery tracking
* **[Preferences](#preferences)** — prebuilt UI for users to manage their notification settings

Available on
<Link href="https://github.com/trycourier/courier-android"><Icon icon="github" iconType="solid" /> GitHub</Link>.

| Requirement     | Value   |
| --------------- | ------- |
| Min Android SDK | 23      |
| Gradle          | 8.4+    |
| Package manager | Jitpack |

## Installation

<Steps>
  <Step title="Add the Jitpack repository">
    In your `settings.gradle` or `settings.gradle.kts`:

    ```gradle theme={null}
    dependencyResolutionManagement {
        repositories {
            google()
            mavenCentral()
            maven { url 'https://jitpack.io' }
        }
    }
    ```
  </Step>

  <Step title="Add the dependency">
    In your app's `build.gradle`:

    ```gradle theme={null}
    dependencies {
        implementation 'com.github.trycourier:courier-android:LATEST_VERSION'
    }
    ```

    Replace `LATEST_VERSION` with the current version from <Link href="https://github.com/trycourier/courier-android/releases">GitHub Releases</Link>.
  </Step>

  <Step title="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.

    ```kotlin theme={null}
    class YourApplication : Application() {
        override fun onCreate() {
            super.onCreate()
            Courier.initialize(this)
        }
    }
    ```
  </Step>
</Steps>

<Note>
  If you only plan to use [`CourierClient`](#courierclient) APIs directly, you can skip the `initialize` step.
</Note>

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

<Note>
  For a full walkthrough of JWT generation, see the [Inbox Authentication guide](/platform/inbox/authentication).
</Note>

<Steps>
  <Step title="Generate a JWT on your backend">
    Call the [Issue Token endpoint](/api-reference/authentication/create-a-jwt) from your server:

    ```bash theme={null}
    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"
      }'
    ```
  </Step>

  <Step title="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.

    ```kotlin theme={null}
    lifecycleScope.launch {
        Courier.shared.signIn(
            accessToken = jwt,
            userId = "your_user_id"
        )
    }
    ```
  </Step>

  <Step title="Sign out when done">
    ```kotlin theme={null}
    lifecycleScope.launch {
        Courier.shared.signOut()
    }
    ```
  </Step>
</Steps>

### EU-hosted workspaces

For EU-hosted Courier workspaces, pass the built-in EU preset through `apiUrls`:

```kotlin theme={null}
lifecycleScope.launch {
    Courier.shared.signIn(
        accessToken = jwt,
        userId = "your_user_id",
        apiUrls = CourierClient.ApiUrls.eu()
    )
}
```

### Authentication state

```kotlin theme={null}
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.

<Tip>
  Inbox requires the [Courier Inbox provider](https://app.courier.com/channels/courier) to be enabled in your workspace. If using JWT authentication, enable JWT support in the provider settings.

  <img src="https://mintcdn.com/courier-4f1f25dc/iZIqSLNN7hLm8RQn/assets/sdks/mobile/courier-jwt-toggle.png?fit=max&auto=format&n=iZIqSLNN7hLm8RQn&q=85&s=214922b19e181b120f70ed4850b69b9a" alt="JWT toggle in Courier provider settings" width="385" style={{ borderRadius: "8px" }} data-path="assets/sdks/mobile/courier-jwt-toggle.png" />
</Tip>

<Note>
  Your app theme must extend `Theme.MaterialComponents` for the prebuilt UI to render correctly. Set this in your `res/values/themes.xml`.
</Note>

<Info>
  For an overview of how Courier Inbox works and how to send messages to it from your backend, see [Get Started with Inbox](/platform/inbox/inbox-overview) and [Send an Inbox Message](/platform/inbox/sending-a-message).
</Info>

### Prebuilt UI

<CodeGroup>
  ```kotlin Jetpack Compose theme={null}
  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())
      }
  )
  ```

  ```kotlin XML Layout theme={null}
  // In your layout XML:
  // <com.courier.android.ui.inbox.CourierInbox
  //     android:id="@+id/courierInbox"
  //     android:layout_width="match_parent"
  //     android:layout_height="match_parent" />

  val inbox: CourierInbox = view.findViewById(R.id.courierInbox)

  inbox.setOnClickMessageListener { message, index ->
      if (message.isRead) message.markAsUnread() else message.markAsRead()
  }

  inbox.setOnLongPressMessageListener { message, index ->
      message.markAsArchived()
  }

  inbox.setOnClickActionListener { action, message, index ->
      Courier.log(action.toString())
  }

  inbox.setOnScrollInboxListener { offsetInDp ->
      Courier.log(offsetInDp.toString())
  }
  ```
</CodeGroup>

### Theming

Pass a `CourierInboxTheme` to customize fonts, colors, unread indicators, swipe actions, and button styles. Both light and dark themes are supported.

```kotlin theme={null}
CourierInbox(
    modifier = Modifier.padding(innerPadding),
    canSwipePages = true,
    lightTheme = yourLightTheme,
    darkTheme = yourDarkTheme,
    ...
)
```

<Frame caption="Default and styled Inbox on Android">
  <div style={{ display: "flex", gap: "16px", justifyContent: "center" }}>
    <img src="https://mintcdn.com/courier-4f1f25dc/iZIqSLNN7hLm8RQn/assets/sdks/mobile/android-inbox-default.png?fit=max&auto=format&n=iZIqSLNN7hLm8RQn&q=85&s=86ae9079bb5fdc4143972dc5d3f9b797" alt="Default Inbox" width="300" style={{ borderRadius: "8px" }} data-path="assets/sdks/mobile/android-inbox-default.png" />

    <img src="https://mintcdn.com/courier-4f1f25dc/iZIqSLNN7hLm8RQn/assets/sdks/mobile/android-inbox-styled.png?fit=max&auto=format&n=iZIqSLNN7hLm8RQn&q=85&s=6bc18219d2a66f2dbba56662f338ac01" alt="Styled Inbox" width="300" style={{ borderRadius: "8px" }} data-path="assets/sdks/mobile/android-inbox-styled.png" />
  </div>
</Frame>

You can also apply branding from [Courier Studio](https://app.courier.com/designer/brands). 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:

```kotlin theme={null}
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

```kotlin theme={null}
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:

```kotlin theme={null}
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.

<Warning>
  **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`.
</Warning>

<Note>
  Push notifications require a physical device and a release build for reliable token registration and delivery. Emulators are best-effort.
</Note>

### Provider Setup

| Provider                                                      | Token Syncing                              |
| ------------------------------------------------------------- | ------------------------------------------ |
| [Firebase FCM](https://app.courier.com/channels/firebase-fcm) | Automatic (via `FirebaseMessagingService`) |
| [Expo](https://app.courier.com/channels/expo)                 | Manual                                     |
| [OneSignal](https://app.courier.com/channels/onesignal)       | Manual                                     |
| [Pusher Beams](https://app.courier.com/channels/pusher-beams) | Manual                                     |

<Info>
  For step-by-step provider credential setup, see the [FCM integration guide](/external-integrations/push/firebase-fcm). Initialize the [Firebase SDK](https://firebase.google.com/docs/android/setup) in your project before continuing.
</Info>

### Add Firebase to your app

In your app's `build.gradle`, declare Firebase Messaging alongside the Courier SDK:

```gradle theme={null}
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](https://firebase.google.com/docs/android/setup) (download `google-services.json`, apply the Google Services plugin, etc.).

### Automatic Token Syncing (FCM)

<Steps>
  <Step title="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.

    ```kotlin theme={null}
    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)
        }
    }
    ```
  </Step>

  <Step title="Register the service in AndroidManifest.xml">
    ```xml theme={null}
    <manifest>
        <application>
            <service
                android:name=".CourierPushNotificationService"
                android:exported="false">
                <intent-filter>
                    <action android:name="com.google.firebase.MESSAGING_EVENT" />
                </intent-filter>
            </service>
        </application>
    </manifest>
    ```
  </Step>

  <Step title="Handle clicks with CourierActivity">
    Extend `CourierActivity` (typically your `MainActivity`) to receive delivery and click events while your app is foregrounded.

    ```kotlin theme={null}
    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
        }
    }
    ```
  </Step>
</Steps>

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

```kotlin theme={null}
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:

```kotlin theme={null}
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:

```kotlin theme={null}
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.

```kotlin theme={null}
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](/api-reference/send/send-a-message) with `push` as the routing channel. See the [FCM sending guide](/external-integrations/push/firebase-fcm#sending-messages) for a complete example.

## Preferences

Courier Preferences provides a prebuilt UI for users to manage which notification topics and channels they subscribe to.

<Info>
  Topics and sections are configured in the [Preferences Editor](/platform/preferences/preferences-editor). See [Preferences Overview](/platform/preferences/preferences-overview) for how preference enforcement works at send time.
</Info>

<CodeGroup>
  ```kotlin Jetpack Compose theme={null}
  CourierPreferences(
      mode = CourierPreferences.Mode.Topic,
      onError = { error ->
          print(error.toString())
      }
  )
  ```

  ```kotlin XML Layout theme={null}
  val preferences: CourierPreferences = view.findViewById(R.id.courierPreferences)

  preferences.apply {
      mode = CourierPreferences.Mode.Topic
      onError = { error ->
          print(error)
      }
  }
  ```
</CodeGroup>

### 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](https://app.courier.com/designer/brands) is automatically applied when a `brandId` is provided.

<Frame caption="Default and styled Preferences on Android">
  <div style={{ display: "flex", gap: "16px", justifyContent: "center" }}>
    <img src="https://mintcdn.com/courier-4f1f25dc/iZIqSLNN7hLm8RQn/assets/sdks/mobile/android-preferences-default.gif?s=748931e662d547de3a1227d9a18b6a85" alt="Default Preferences" width="250" style={{ borderRadius: "8px" }} data-path="assets/sdks/mobile/android-preferences-default.gif" />

    <img src="https://mintcdn.com/courier-4f1f25dc/iZIqSLNN7hLm8RQn/assets/sdks/mobile/android-preferences-styled.gif?s=0ee58d84f4f57937134e5b816543e33a" alt="Styled Preferences" width="250" style={{ borderRadius: "8px" }} data-path="assets/sdks/mobile/android-preferences-styled.gif" />
  </div>
</Frame>

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

```kotlin theme={null}
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

```kotlin theme={null}
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

```kotlin theme={null}
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:

```kotlin theme={null}
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

```kotlin theme={null}
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

```kotlin theme={null}
val brand = client.brands.getBrand(brandId = "...")
```

### URL Tracking

Pass any tracking URL found inside a push notification payload or inbox message:

```kotlin theme={null}
// 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](https://www.courier.com/docs/reference).

<CardGroup cols={2}>
  <Card title="Inbox Overview" href="/platform/inbox/inbox-overview" icon="inbox">
    Learn about Courier Inbox and how to set it up
  </Card>

  <Card title="Push Integrations" href="/external-integrations/push/intro-to-push" icon="mobile">
    Configure FCM and other push providers
  </Card>

  <Card title="Preferences" href="/platform/preferences/preferences-overview" icon="list">
    Set up notification preference topics and channels
  </Card>

  <Card title="GitHub" href="https://github.com/trycourier/courier-android" icon="github">
    Source code, examples, and changelog
  </Card>
</CardGroup>
