Guides/How to Add a Notification Center to Your App/How to Add a Notification Center to Your App

Chapter 2

How to Add a Notification Center to Your App

How to add an in-app notification center to a web app or mobile app: the three-layer architecture, then working React, iOS, Android, and React Native setup with the constraints of each.

How to Build a Notification Center

Last updated: July 2026

Key takeaways

  • An in-app notification center is three layers working together: a backend that decides what to send, a delivery layer that gets it there in real time, and a frontend that renders the inbox.
  • Real-time delivery means WebSockets, not polling. That's where most of the hard engineering lives: reconnection, offline sync, and connection health.
  • With Courier, you install one SDK, call signIn with a user ID and a backend-generated JWT, and render the CourierInbox component. The same pattern holds across React, iOS, Android, React Native, and Flutter.
  • One API call sends the same notification to the in-app inbox, email, push, SMS, and chat, and adapts the content to each.
  • A web app and a mobile app follow the same install, sign-in, render pattern, but under different constraints: browser tab lifecycle and optional web push on web; OS app suspension, push permission, and native rendering on mobile.

Architecture overview

Adding an in-app notification center means coordinating three layers that have to work together smoothly enough that users never think about the complexity underneath.

Notification center architecture: your app, the notification service, the delivery layer, and where notifications land

The backend layer is where everything starts. Your app triggers an event whenever something happens that users might care about: someone comments, an order ships, a payment fails. The notification API receives that event and works out who should get it, what it should say, and where it should go. Routing decides which channels to use based on your rules and each user's preferences. This layer is mission control, handling the logic before anything reaches a user.

The delivery layer is all infrastructure. WebSocket connections handle real-time delivery, because HTTP polling is too slow for what users expect now. Message persistence makes sure nothing is lost if a user is offline when the notification fires. State synchronization keeps everything consistent across devices. And integration with APNs and FCM handles mobile push. This layer doesn't make decisions, it executes on them reliably.

The frontend layer is what users see and touch, across your web app and your mobile apps. UI components render the inbox, toasts, and badges. State management tracks which notifications are read, unread, or archived. Authentication keeps it secure. Real-time updates make new notifications appear without any action from the user.

Every layer has failure modes. WebSockets drop. Databases go down. Users lose connectivity. Mobile apps get suspended by the OS. A reliable notification center needs retry logic, queuing, fallbacks, and more monitoring than you'd expect. This is why teams who start building in-house often either abandon the project or ship something that technically works but doesn't feel reliable.

SDK and platform support

Courier ships native SDKs for every major platform, so the same in-app notification center works across your web app, iOS app, and Android app. Install method and minimum versions vary.

PlatformPackageInstall methodMinimum version
Web (React)@trycourier/courier-reactnpmReact 16+
Web (vanilla JS)@trycourier/courier-ui-inboxnpmAny framework
iOScourier-iosSwift Package ManageriOS 15+
Androidcourier-androidGradle via JitPackAndroid 5.0+
React Native@trycourier/courier-react-nativenpm + CocoaPodsiOS 15+, Android 5.0+
Fluttercourier-flutterpub.devFlutter 3.0+

Each SDK ships the same core capabilities: real-time delivery over WebSockets, cross-device state sync, and the pre-built CourierInbox component. The component renders with a default theme and accepts configuration objects for custom styling.

Core capabilities

Real-time delivery

Real-time delivery is non-negotiable for a modern in-app notification center, and it means WebSocket connections, not HTTP polling. The moment someone comments on a post or approves a request, the notification should show up with no action from the user.

The catch is that WebSockets are stateful connections, so they break in ways HTTP requests don't. Users lose connectivity constantly: elevators, tunnels, switching from WiFi to cellular, apps backgrounding. Your code needs connection recovery with automatic reconnection, re-authentication, and syncing whatever arrived while disconnected. Connection health becomes its own job, with heartbeats, timeouts, and exponential backoff on reconnection.

Cross-channel state sync

Cross-channel state sync sounds simple until you build it. You send a notification over inbox and email at the same time. The user sees the email first, opens it, reads it. Then they open your mobile app, and the inbox notification is already marked as read, because the system knows they handled it over email.

The same has to work for SMS and push. A user taps a link in an SMS, the inbox message updates. They dismiss a push, the inbox archives. The goal is making the whole thing feel like one coherent system instead of separate channels sending similar messages. Courier does this automatically: when a notification goes out over multiple channels, the backend tracks interaction events from all of them, flows them into a central state store, and propagates any change everywhere.

Multi-device sync

Users switch between your web app, iOS app, and Android app throughout the day, and the in-app inbox needs to stay in sync across all of them without anyone thinking about it. Read something on your phone during your commute, and it's marked read on your laptop when you get to the office.

The hard part is keeping a single source of truth while devices aren't always connected, clocks aren't perfectly synced, and a user might act on two devices in quick succession. That takes conflict resolution, offline queuing for actions taken while disconnected, and an efficient sync protocol.

Preferences

Preference management is essential, and plenty of systems get it wrong. Users need control over what they receive and how:

  • Choose the notification types they care about (mentions yes, routine updates no)
  • Pick channels per type (approvals over inbox and email, updates over inbox only)
  • Set global rules ("nothing after 8pm")
  • Manage subscription topics (billing yes, marketing no)

Preference management interface with per-type and per-channel controls

Courier's preference center is hosted, so you don't build and maintain the preference UI yourself. The backend enforces these preferences automatically when it routes a notification, and it handles GDPR and CCPA requirements.

Adding it to your app

The pattern is the same everywhere: install the SDK, sign in with a user ID and a backend-generated JWT, and render the component. What changes between a web app and a mobile app is the constraints around that pattern.

Web app

In a web app the notification center renders in the browser, as a dropdown from the bell or a full page, styled with your theme or CSS. Real-time delivery runs over a WebSocket (WSS) that the tab holds while it's open, and the SDK reconnects when the tab wakes or regains focus. There's no OS push registration for the in-app inbox itself; browser push, for when the tab is closed, is a separate opt-in.

React

React is the fastest path. Install the package:

npm install @trycourier/courier-react

Add the inbox component:

import { CourierInbox, useCourier } from "@trycourier/courier-react";
import { useEffect } from "react";
export default function NotificationCenter() {
const courier = useCourier();
useEffect(() => {
// Generate the JWT on your backend, never in client-side code
courier.shared.signIn({
userId: "user_123",
jwt: "your_jwt_token",
});
}, []);
return (
<div>
<h1>Notifications</h1>
<CourierInbox />
</div>
);
}

Sign in once, whether you're using the inbox, toasts, or other SDK attributes. The SDK handles state, real-time sync, and backend communication.

Vanilla JavaScript

Not on React? The web components SDK works with any framework, or none:

npm install @trycourier/courier-ui-inbox
<body>
<courier-inbox></courier-inbox>
<script type="module">
import { Courier } from "@trycourier/courier-js";
// JWT generated on your backend
Courier.shared.signIn({
userId: "user_123",
jwt: "your_jwt_token",
});
</script>
</body>

Mobile app

In a mobile app the inbox renders as a native view, with native scrolling, theming, and performance, not a web view. Two constraints shape the setup. First, the OS suspends backgrounded apps, so the SDK manages the connection lifecycle: reconnecting on foreground, restoring state, and refreshing the feed. Second, to alert users when the app is closed you register for push through APNs on iOS or FCM on Android, and the in-app inbox and push share read state. Minimum versions are iOS 15+ and Android 5.0+.

iOS

Add the package over Swift Package Manager:

dependencies: [
.package(
url: "https://github.com/trycourier/courier-ios",
from: "5.0.0"
)
]

Then authenticate and display:

import Courier_iOS
// JWT generated on your backend
Courier.shared.signIn(
userId: "user_123",
jwt: "your_jwt_token"
)
// SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Text("Notifications")
CourierInboxView()
}
}
}

Recent iOS releases added long-press gestures with haptic feedback, pull-to-refresh on an empty inbox, and steadier WebSocket handling across background and foreground transitions.

Android

Add the SDK to your Gradle dependencies, pinning a version rather than tracking latest:

repositories {
google()
mavenCentral()
maven { url 'https://www.jitpack.io' }
}
dependencies {
implementation 'com.github.trycourier:courier-android:5.0.0'
}

Then authenticate and render the inbox. CourierInbox is a Compose component, so host it from a ComponentActivity with setContent:

import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import com.courier.android.Courier
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// JWT generated on your backend
Courier.shared.signIn(
userId = "user_123",
jwt = "your_jwt_token"
)
setContent {
CourierInbox()
}
}
}

React Native

One codebase for both an iOS app and an Android app. Install the package and native dependencies:

npm install @trycourier/courier-react-native
cd ios && pod install

Then use it in your components:

import { Courier, CourierInbox } from "@trycourier/courier-react-native";
import { useEffect } from "react";
export default function NotificationScreen() {
useEffect(() => {
// JWT generated on your backend
Courier.shared.signIn({
userId: "user_123",
jwt: "your_jwt_token",
});
}, []);
return <CourierInbox />;
}

Sending notifications

A rendered inbox is empty until you send notifications to it. The simplest is an API call with a recipient, content, and routing:

const { requestId } = await courier.send({
message: {
to: { user_id: "user_123" },
content: {
title: "New comment",
body: "Sarah commented on your post",
},
routing: {
method: "all",
channels: ["inbox"],
},
},
});

Multi-channel delivery

Multi-channel delivery replaces separate per-channel code with one API call that targets all of them:

const { requestId } = await courier.send({
message: {
to: {
user_id: "user_123",
email: "user@example.com",
phone_number: "+1234567890",
},
content: {
title: "Order shipped",
body: "Your order #12345 has shipped and arrives tomorrow",
},
routing: {
method: "all",
channels: ["inbox", "email", "push", "sms"],
},
},
});

The same content adapts to each channel automatically. The inbox gets rich text and action buttons. Email renders as HTML. Push condenses to a lock-screen format. SMS becomes plain text with shortened links.

The routing engine controls how channels interact. Send to everything at once with all, or try channels in sequence with fallbacks using single:

routing: {
method: "single",
channels: ["push", "sms", "email"]
}

That tries push first, then SMS, then email.

Using templates

Courier template designer showing push and email notification templates

When you're sending the same type of notification over and over, templates beat hardcoding content:

const { requestId } = await courier.send({
message: {
to: { user_id: "user_123" },
template: "comment-notification",
data: {
commenter: "Sarah",
post_title: "Q4 Planning",
comment_preview: "Great insights on the roadmap...",
},
},
});

Templates live in Courier's designer, where non-technical teammates can edit content without touching code, and they adapt across channels automatically.

Customization

Theming works through configuration objects where you set colors, fonts, border radius, and spacing. The inbox component applies them throughout.

A Twilio-branded inbox showing the notification center themed to match a product

Courier also gives you brand settings in the dashboard, so you configure your design system once and apply it across every notification. Dark mode is built in.

The built-in views cover unread, all, and archived. For anything more specific, you can build custom filtered views by tag, category, date, or any other metadata.

Custom action buttons on a notification, deep-linking to specific screens

Action buttons turn notifications into interactive workflows. An approval notification gets approve and reject; a meeting invite gets accept and decline. Those actions can deep-link to a screen or trigger a custom handler.

Digest notifications help manage volume. Instead of 15 separate notifications about thread comments, you send one that says "15 new comments on Q4 Planning." Courier groups similar notifications by time window, combines them, and sends at a sensible time.

Frequently asked questions

How long does it take to add a notification center with Courier?

The pre-built CourierInbox component can be running in an existing app in a few hours. The React, iOS, Android, Flutter, and React Native SDKs all follow the same pattern: install the package, call signIn with a user ID and a backend-generated JWT, and render the component. Theming and your first API send usually add another day. The work that takes weeks from scratch, WebSocket infrastructure, state management, and cross-device sync, is handled by the SDK.

How does JWT authentication work for the notification center?

Each user session needs a signed JWT generated on your backend with your Courier API key. The JWT holds the user ID and an expiration, and your app passes it to Courier.shared.signIn() on the client. Courier validates the token before opening the WebSocket connection. When it expires, you generate a new one on your backend and call signIn again. The authentication docs cover token generation for Node, Python, Go, Ruby, Java, C#, and PHP.

What happens to notifications sent while a user is offline?

They're persisted and delivered when the connection comes back. The inbox loads unread notifications on reconnection, so users see everything they missed with no extra code. State (read, unread, archived) stays consistent because it's managed server-side, not in local storage.

Can I use the notification center alongside push notifications?

Yes. Courier's cross-channel integration means the inbox and push share state. Open a push, and the matching inbox notification is marked read. Read a message in the inbox, and the push badge count updates. It works across iOS, Android, and web push from one API call that targets every channel at once.

A mobile push notification sharing read state with the in-app inbox

How do I customize the appearance of the notification center?

The CourierInbox component takes a theme configuration object where you set colors, fonts, border radius, and spacing, and the dashboard brand settings apply your design system globally. Dark mode is built in. When the config options aren't enough, the SDKs support custom component rendering, so you can replace any part of the UI with your own while keeping the underlying state management and delivery.

Previous chapter

What Is an In-App Notification Center?

What an in-app notification center is, how it differs from push, toast, and email, and the components that separate a basic inbox from a great one.

Next chapter

Notification Center Best Practices

How to design notifications users actually read: content, format, batching, preferences, performance, security, and the different constraints on web and mobile apps.

Multichannel Notifications Platform for SaaS

Products

Platform

Integrations

Customers

Blog

API Status

Subprocessors

© 2026 Courier. All rights reserved.