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 .
Requirement Value Min Android SDK 23 Gradle 8.4+ Package manager Jitpack
Installation
Add the Jitpack repository
In your settings.gradle or settings.gradle.kts: dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url 'https://jitpack.io' }
}
}
Add the dependency
In your app’s build.gradle: dependencies {
implementation 'com.github.trycourier:courier-android:5.2.14'
}
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.
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"
}'
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"
)
}
Sign out when done
lifecycleScope. launch {
Courier.shared. signOut ()
}
You can listen for authentication state changes:
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.
Your app theme must extend Theme.MaterialComponents for the prebuilt UI to render correctly. Set this in your res/values/themes.xml.
Prebuilt UI
Jetpack Compose
XML Layout
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,
.. .
)
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. archiveMessage (messageId = "..." )
Courier.shared. readAllInboxMessages ()
}
// Or via message object
message. markAsRead ()
message. markAsUnread ()
message. markAsArchived ()
Push Notifications
The SDK simplifies push notification setup with automatic FCM token syncing, delivery tracking, and permission management.
Push notifications require a physical device. Emulators do not reliably support push token registration or notification delivery.
Provider Setup
Provider Token Syncing Firebase FCM Automatic (via CourierService) Expo Manual OneSignal Manual Pusher Beams Manual
Automatic Token Syncing (FCM)
Create a notification service
Create a class that extends CourierService. This automatically syncs FCM tokens and tracks delivery. import com.courier.android.notifications.presentNotification
import com.courier.android.service.CourierService
import com.google.firebase.messaging.RemoteMessage
class YourNotificationService : CourierService () {
override fun showNotification (message: RemoteMessage ) {
super . showNotification (message)
message. presentNotification (
context = this ,
handlingClass = MainActivity:: class .java,
icon = android.R.drawable.ic_dialog_info
)
}
}
Register the service in AndroidManifest.xml
< service
android:name = ".YourNotificationService"
android:exported = "false" >
< intent-filter >
< action android:name = "com.google.firebase.MESSAGING_EVENT" />
</ intent-filter >
</ service >
Extend CourierActivity for click handling
class MainActivity : CourierActivity () {
override fun onPushNotificationClicked (message: RemoteMessage ) {
// Handle notification click
}
override fun onPushNotificationDelivered (message: RemoteMessage ) {
// Handle notification delivery
}
}
Manual Token Syncing
If you don’t want to use CourierService, sync tokens manually:
lifecycleScope. launch {
Courier.shared. setToken (
provider = CourierPushProvider.FIREBASE_FCM,
token = "your_fcm_token"
)
}
Requesting Permission
For Android 13+ (API 33), you need to request notification permission at runtime. You can also check the current status without prompting.
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.
Jetpack Compose
XML Layout
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.
CourierClient
For advanced use cases, CourierClient provides direct access to the Courier API:
val client = CourierClient (
jwt = "..." ,
userId = "your_user_id"
)
// Token management
client.tokens. putUserToken (token = "..." , provider = "firebase-fcm" )
// Inbox
val messages = client.inbox. getMessages (paginationLimit = 25 )
val unreadCount = client.inbox. getUnreadMessageCount ()
client.inbox. read (messageId = "..." )
client.inbox. readAll ()
// Inbox websocket (real-time updates across devices)
client.inbox.socket. apply {
receivedMessage = { message -> print (message) }
receivedMessageEvent = { event -> print (event) } // READ, UNREAD, ARCHIVE, OPENED
connect ()
sendSubscribe ()
}
// Preferences
val prefs = client.preferences. getUserPreferences ()
client.preferences. putUserPreferenceTopic (
topicId = "..." ,
status = CourierPreferenceStatus.OPTED_IN,
hasCustomRouting = true ,
customRouting = listOf (CourierPreferenceChannel.PUSH)
)
// Branding
val brand = client.brands. getBrand (brandId = "..." )
// URL tracking (from push payloads or inbox messages)
client.tracking. postTrackingUrl (url = "courier_tracking_url" , event = CourierTrackingEvent.DELIVERED)
See the full API reference on GitHub .