
Flutter is built for shipping fast. That's the whole point. Single codebase, consistent UI across iOS and Android, hot reload that actually works. You picked Flutter because you wanted to stop rewriting the same screens twice.
So why are you still hand-rolling notification infrastructure?
Building an in-app inbox from scratch takes weeks. Maintaining it takes forever. Or you can add one in 10 lines of code and get back to building what actually differentiates your app.
Install the package:
flutter pub add courier_flutterSign in your user (typically at login):
import 'package:courier_flutter/courier_flutter.dart';
_signIn() async {
await Courier.shared.signIn(
accessToken: 'your_users_jwt', // Generate this on your backend
userId: 'your_user_id',
);
}Add the inbox widget:
import 'package:courier_flutter/ui/inbox/courier_inbox.dart';
@override
Widget build(BuildContext context) {
return CourierInbox(
onMessageClick: (message, index) {
message.isRead ? message.markAsUnread() : message.markAsRead();
},
);
}Add user preferences if you want them:
import 'package:courier_flutter/ui/preferences/courier_preferences.dart';
@override
Widget build(BuildContext context) {
return CourierPreferences(
mode: TopicMode(),
);
}That's the whole inbox integration. The SDK handles real-time sync and read/unread state. Push takes a little native wiring on top: your iOS AppDelegate inherits from CourierFlutterDelegate, and on Android a FirebaseMessagingService hands FCM tokens and messages to the SDK. From there the SDK syncs tokens for both APNs and FCM. The mobile push setup guide walks through both platforms.
With no theme, the inbox widget takes its styling from your app's Flutter Theme: button style from elevatedButtonTheme, loading and unread indicator color from primaryColor, and text styles from textTheme. If you're using Material 3 or a custom theme, the inbox picks up those defaults without configuration. When you need deeper customization, pass a CourierInboxTheme through the lightTheme and darkTheme parameters.

import 'package:courier_flutter/ui/inbox/courier_inbox_theme.dart';
final theme = CourierInboxTheme(
loadingIndicatorColor: Color(0xFF6C5CE7),
unreadIndicatorStyle: CourierInboxUnreadIndicatorStyle(
indicator: CourierInboxUnreadIndicator.dot,
color: Color(0xFF6C5CE7),
),
titleStyle: CourierInboxTextStyle(
unread: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
read: TextStyle(fontSize: 16),
),
bodyStyle: CourierInboxTextStyle(
unread: TextStyle(fontSize: 14, color: Colors.grey[700]),
read: TextStyle(fontSize: 14, color: Colors.grey[500]),
),
);
CourierInbox(
lightTheme: theme,
darkTheme: theme,
onMessageClick: (message, index) {
message.isRead ? message.markAsUnread() : message.markAsRead();
},
)You can override:
The widget uses Flutter's standard theming system throughout, so customization follows patterns you already know.
If you're building on Flutter, you're probably already using Firebase Cloud Messaging for push. So why add another SDK?
FCM handles delivery. It gets a notification from your server to the device. But that's where it stops. You still need to build the inbox UI where users see past notifications, read/unread state that persists across app launches, a preferences screen where users control what they receive, cross-channel orchestration for email and SMS and push and in-app messages, and read state sync so when a user opens an email, the related inbox message marks as read.
That last one is harder than it sounds. When someone opens an email at their desk, your app should know that message is read before they even unlock their phone. Courier can mark the inbox message read when the recipient opens the email. That cross-channel state is what takes weeks to build correctly, and with Courier you don't build it.
FCM and Courier aren't competing. FCM is your delivery pipe. Courier is the orchestration layer sitting on top, handling inbox UI, preferences, routing logic, and the state management that ties everything together. You'll likely use both. The SDK syncs your FCM tokens to the signed-in user in Courier, including the new token FCM issues when someone reinstalls your app.
Here's what's easy to underestimate when you start building notification infrastructure in-house:
That's not a sprint. That's a quarter. And then someone has to maintain it while your app evolves.
When Twilio needed to unify notifications for their own platform serving 10+ million developers, they chose Courier. As their Technical Lead put it: "We chose Courier because the depth of the inbox and multi-channel integrations allowed us to choose one notification platform for all products and teams at Twilio."
π¬ Inbox:
Real-time updates via WebSocket, read/unread state management, swipe actions with haptic feedback, long-press handling, pull-to-refresh with proper empty states, brand color integration for unread indicators, and pagination. Courier can also mark the inbox message read when the recipient opens the email.
βοΈ Preferences:
Topic-based and channel-based modes, syncs to Courier's backend automatically, and themes to match your app. When a user toggles a preference, your send logic respects it without extra code on your end.
π Push:
Token sync for both APNs and FCM, including the fresh token FCM issues after a reinstall, plus delivery and click tracking that lands in Courier's logs. Clicks are tracked whatever state your app is in. On iOS, add a Notification Service Extension to track delivery while your app is backgrounded or closed.
π Digests:
Schedule notifications on daily, weekly, or custom intervals. Batch multiple events into single notifications, configure per-user (some want real-time, others want daily summaries), and works across all channels so you can combine ten activity events into one email or push. The digest logic runs on Courier's backend, which means you don't need to track pending notifications or build batch-send infrastructure.
The SDK supports iOS 15.0+, Android SDK 23+, and Dart 3.3.0+. Beyond the Flutter SDK, Courier connects to 50+ providers including SendGrid, Twilio SMS, Slack, and Teams. If you're building a SaaS product, the same notification infrastructure handles onboarding flows, payment reminders, and multi-channel orchestration.
Under GDPR, consent isn't a one-time checkbox. People can object to direct marketing at any time (Article 21), and you have to stop when they do. Building a preference center that actually works means:
Courier's preference widget handles the UI. When a user toggles a preference, it syncs to Courier's backend immediately. Your send logic automatically respects those preferences without extra checks on your end. You send a "product_updates" notification, Courier checks that user's preference for the topic, and only delivers if they allow it. The preference data is stored per user in Courier, which means it persists across devices and works the same whether you're sending push, email, or SMS.
This isn't compliance theater. When users feel in control, they leave notifications enabled instead of revoking permissions at the OS level. That's worth the integration cost alone.
flutter pub add courier_flutterGenerate a JWT on your backend for user authentication, call Courier.shared.signIn() when your user logs in, and add CourierInbox and CourierPreferences widgets where they fit your UX.
Full documentation: github.com/trycourier/courier-flutter

How to protect your email domain and sending reputation
AI agents have made spoofing cheap, and a DMARC record at p=none won't stop it. Here's how to enforce DMARC without bouncing your own mail, keep password resets apart from newsletters, protect your sending reputation, read Google Postmaster Tools, and audit your domain with six AI prompts.

Courier's Response to the eMudhra and PHP Security Issues
Courier's assessment of the eMudhra certificate authority compromise and the October 21 PHP security release: what happened, the steps we're taking, and what you may want to check in your own environment.

Control how fast Courier sends to your providers
You shouldn't have to think about how fast you send to Courier. Set a send rate limit on any provider, and Courier sends your notifications to it at the rate you choose. Anything over the limit is queued, not dropped.