Kyle Seyler
September 05, 2025

Table of contents
Table of Contents
Introduction
What You'll Build
What You'll Need
Part 1: Event-Driven Onboarding
Part 2: Multi-Step Sequences
Part 3: Smart Channel Routing
Part 4: Personalization and Segmentation
Part 5: In-App Tasks with Courier Inbox
Part 6: Multi-Tenant Configuration
Part 7: Analytics and Observability
Testing Your Onboarding
Conclusion
Resources
TL;DR
This guide shows you how to build a sophisticated onboarding system using Courier that automatically triggers based on user actions, routes messages through multiple channels (email, push, SMS, in-app), personalizes content by user segment, and escalates high-value accounts to your team. Instead of building complex state machines and managing multiple providers, you'll use Courier's visual automation builder, multi-channel templates, and built-in analytics to create onboarding flows that adapt to user behavior in real-time.
You've built something people want to use. Now you need to help them actually use it. Good onboarding is what turns signups into active users, but building it right means coordinating multiple notification channels, tracking user progress, personalizing content based on user type, and making it all work consistently across web and mobile.
The traditional approach involves stitching together email providers, building state machines for multi-step flows, implementing retry logic, and creating branching logic for different user paths. This guide details the way to build a complete onboarding system using Courier's platform which can handle all the complex orchestration while you focus on the user experience.
We'll create an onboarding system that:
Throughout this guide, we'll use patterns apply to any product that needs sophisticated onboarding. You'll see real Courier SDK code that you can adapt to your needs, along with explanations of why each piece matters for your users.

First, let's get the foundations in place. Install the Courier SDK for your platform:
Copied!
npm install @trycourier/react-inbox # React Inbox
Now initialize Courier with your authentication token:
Copied!
const { CourierClient } = require("@trycourier/courier");const courier = new CourierClient({authorizationToken: process.env.COURIER_AUTH_TOKEN});
See the authentication docs for more setup options.
This onboarding system uses several Courier capabilities that require initial configuration:
Event Integration
analytics.track, analytics.identify) that powers automation triggersNotification Channels
Multi-Tenant Configuration (if applicable)
Analytics and Monitoring
Start with email and Segment integration for a basic implementation, then add additional channels as needed.
Traditional onboarding sends emails on a fixed schedule - day 1, day 3, day 7. But your users don't follow schedules. Some dive in immediately and need advanced features explained. Others sign up and disappear for a week. Event-driven onboarding responds to what users actually do, creating a more relevant experience.
When you track user events, you can:
In this example we will use our Twilio Segment integration with events:
analytics.group
analytics.identify
analytics.track
To begin, we're interested in using the analytics.track method to trigger different flows based on sign-up, login, and project_start. Using Segment, you can pull in these events for behaviour-based messaging automation.
Copied!
// track the key eventsanalytics.track("sign-up", {productType: "saas",segment: "midmarket"});// Later when user takes actionsanalytics.track("login", { user_id: userId });analytics.track("project_start", { user_id: userId, project_name: "My First Project" });
Every event updates the user profile, creating a complete picture of their journey. This accumulated data helps you make smarter decisions about what to send next:
These profiles become the foundation for all your routing and personalization decisions.
Your onboarding shouldn't be a one-size-fits-all drip campaign. Users progress at different speeds, and your sequences need to adapt. Courier's automations let you combine time-based delays with behavioral triggers, creating flows that feel personalized without building separate systems for each user type.
The key is balancing:
[PLACEHOLDER: Visual flow showing a multi-step sequence with branches based on user behavior]
Here's a practical automation that adapts to user behavior, built with drag-and-drop tools in the Courier app.

Courier supports various timing strategies to respect user preferences:
Your sequences can also include escalation logic for when automation isn't enough. For enterprise customers or high-value accounts, you might want human intervention when they get stuck. This can be built right into your automation flows.
How it works in Courier: You add escalation as conditional steps in your automation. After a wait period or activity check, add a send step with an if condition to evaluate the user's status. If they meet your escalation criteria (enterprise plan + no activation), the step triggers a notification to your team:


Copied!
{"action": "send","template": "team-escalation-alert","recipient": "success-team","if": "profile.plan === 'enterprise' && profile.activated === false","profile": {"slack": {"access_token": "xoxb-xxxxx","channel": "success-alerts"}}}
The key is being selective about what triggers escalation - account value, time since signup without key actions, or explicit help requests are good indicators. Your Slack message includes all the context your team needs: customer details, their progress, and direct links to help them.
This approach keeps high-value customers from falling through the cracks while avoiding alert fatigue from lower-priority users. You can also route different segments to different teams - enterprise to success managers, mid-market to support, etc.
See the automation documentation for more complex flow examples including escalation patterns.
Not every message should go to email. Your power users might prefer Slack notifications. Mobile users need push notifications. Some messages are urgent enough for SMS. The challenge is routing each message through the right channel without building complex if/else logic throughout your codebase.
Courier's routing engine handles this complexity for you. You define your routing strategy once, and Courier automatically:
Here's where Courier gets really powerful. Your users can set their notification preferences (through Courier's embeddable preference center or your own UI), and these preferences automatically influence routing decisions. Courier gives both developers and users control over the channels they want to allow for each topic and urgency level.
Think of it as a two-layer system:
When a message is sent, Courier evaluates both layers. If a user has explicitly opted out of email, Courier won't even attempt that channel - it'll skip straight to your fallback options.

Here's a look at the embeddable user preference center.

The method parameter in your routing config is crucial:
Copied!
// SINGLE: Try channels in order until one succeedsrouting: {method: "single",channels: ["email", "push", "sms"]}// Result: Sends to email. If that fails/bounces, tries push.// If push fails, tries SMS. Stops at first success.// ALL: Send to every available channel simultaneouslyrouting: {method: "all",channels: ["email", "push", "inbox"]}// Result: User gets the message in email AND push AND inbox// Great for critical alerts where redundancy matters
Courier's failover is smarter than just "try the next channel." Here's what actually happens:
Here's a complete example showing how it all works together:
Copied!
const { requestId } = await courier.send({message: {to: {user_id: userId,email: "user@example.com",phone_number: "+1234567890"},routing: {method: "single", // Try channels in orderchannels: ["email", "inbox", "sms"]},timeout: {channel: 3600000, // Wait up to 1 hour per channelprovider: 300000 // Wait up to 5 min per provider},providers: {// Optional: specify backup providersemail: {override: "sendgrid",if_unavailable: "smtp"}}}});
What happens in this flow:
| Channel | Best For | Avoid For | User Expectation |
|---|---|---|---|
| Detailed content, records | Urgent alerts | Check periodically | |
| Push | Time-sensitive updates | Long content | Immediate attention |
| SMS | Critical alerts only | Marketing | Very urgent only |
| In-app | Task lists, history | Time sensitive alerts | Check when in app (web/mobile) |
| Slack | Team notifications | Personal data | Work context |
Remember: Start simple with email + in-app, then add channels as needed. See the routing documentation for advanced patterns.
Generic onboarding frustrates everyone. Enterprise customers expect white-glove treatment. Developers want API docs upfront. Small teams need quick wins. The solution isn't building separate flows for each segment - it's creating one smart system that adapts.
With Courier, you can personalize based on:
Courier is both a developer-friendly tool and a platform for non-technical teams. The visual template designer typifies this contrast better than most features. Instead of writing HTML, CSS, and channel-specific code, you can drag and drop components to build beautiful, responsive templates that automatically work across email, push notifications, SMS, and in-app messages. Developers can set up the data connections and conditional logic, while designers and product managers can iterate on the visual design and copy without touching code. This collaboration speeds up your onboarding development significantly.
What makes it special:
Here's how to build a personalized template that works across all your channels:
Design in the Visual Editor In Courier's template designer:

Map Segment Data to Template Variables When Segment sends user traits, they automatically fill your template placeholders:
Copied!
// Segment sends this dataanalytics.identify(userId, {name: "Sarah Chen",company: "TechCorp",plan: "enterprise",industry: "fintech"});
Your template in the visual designer uses these traits: ```handlebars Hi {{analytics.traits.name}},
Welcome to our platform! Since {{analytics.traits.company}} is in the {{analytics.traits.industry}} space, here are some relevant resources...
{{#if (eq analytics.traits.plan "enterprise")}} Your dedicated success manager will reach out within 24 hours. {{else}} Here are three quick wins to get started: {{/if}} ``` Trigger Your Onboarding Flow Once you've designed your templates, you set up an automation in Courier that defines the sequence and timing. Then when a user signs up (or hits other milestones), that event triggers the automation to start. From there, Courier handles everything, from sending the welcome email and waiting for user activity, to checking conditions and routing to the right channels.
The workflow runs automatically using the templates you designed and the real user data from Segment. One template design works across all channels, and the whole sequence adapts based on what each user actually does.
Emails get lost. Users forget what they need to do. There's no sense of progress. That's why modern products include in-app task lists for onboarding - giving users a persistent checklist they can work through at their own pace.
Courier Inbox is essentially a notification center that lives inside your application. Think of it like the notifications you see in Facebook or Slack, but embedded in your product. Users can see all their messages, mark them as read, archive them, and take actions directly from the interface. The best part is that it syncs in real-time across all platforms, like when you mark something as read on web and it's instantly read on mobile too.
Courier Inbox provides this out of the box:
Most teams create their onboarding tasks through Courier's platform interface, then simply embed the Inbox component in their React or React Native apps. The Inbox automatically pulls in all messages sent to the "inbox" channel, handles state management, and provides a clean Gmail-like interface for users.

After sending tasks to the inbox from your backend (using courier.send() with channels: ["inbox"]), display them in your application using our easy to install Courier-Inbox SDK. In the case below, just a handful of code in the front end gets you a popup notification center.
Copied!
import { useEffect } from 'react';import { CourierInbox, useCourier } from '@trycourier/courier-react';export default function App() {const courier = useCourier();useEffect(() => {// Generate a JWT for your user (do this on your backend server)const jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'; // Replace with actual JWT// Authenticate the user with the inboxcourier.shared.signIn({userId: $YOUR_USER_ID,jwt: jwt,});}, []);return (<div style={{ padding: '24px' }}><CourierInboxPopupMenu /></div>);}
The beauty of this approach is that you can customize tasks based on user types. Enterprise users might see "Schedule onboarding call" and "Configure SSO" while developers get "Generate API keys" and "Review webhook docs." When users complete tasks, you can track their progress in user profiles and trigger success messages or next-step automations when they finish their onboarding checklist.
The Inbox component handles all the complexity - real-time updates, persistence, and state management. Your users get a Gmail-like experience for their onboarding tasks. Not only does it work on web but it translates seamlessly to mobile using our iOS, Android, Flutter, and React Native SDKs.
Cross-platform benefits:
See the Inbox documentation for styling and customization options.
If you're building B2B SaaS, you know that different customer segments need different experiences. Enterprise customers expect their branding, custom workflows, and dedicated support. Startups want self-service and community. Trial users need convincing.
Courier's tenant system lets you create these differentiated experiences without maintaining separate codebases:
Courier's tenant system supports complex organizational structures that mirror how businesses actually operate:
Copied!
Organization (tenant0)└── Workspace (tenantQ)└── Team (tenantP)├── Project (tenantR1)│ └── Environment (tenantR1D1)└── Project (tenantR2)├── Environment (tenantR2D1)└── Environment (tenantR2D2)
This hierarchy enables several powerful features:
Inheritance: Settings flow from parent to child with override capabilities. An organization might set default branding that workspaces inherit, but teams can override with their specific colors.
Scoped notifications: Send to all members of a tenant and its children using audience targeting. A workspace-level alert automatically reaches all teams and projects within it.
Context preservation: Maintain organizational boundaries in notification delivery through user profiles, ensuring the right people get the right messages with appropriate branding.
Users exist in multiple contexts simultaneously. A developer might receive notifications as an individual (personal preferences), a team member (team notifications), and a workspace participant (organization-wide alerts). When sending notifications, you specify the tenant context to ensure the right preferences and branding are applied:
Copied!
{"message": {"to": {"user_id": "user1","context": {"tenant_id": "production-workspace"}},"content": {"title": "Deployment completed","body": "Your app in {$.tenant.name} is now live"}}}
You'll configure your tenant brands and segments through Courier's platform interface. This lets you set up custom logos, colors, messaging tone, and channel preferences for each level of your organizational hierarchy without writing code. Once configured, your templates and automations automatically use the right branding based on the tenant context.
When organizing your tenant strategy, think about how each customer segment should experience your product. Enterprise customers typically expect custom logos and colors, formal messaging, dedicated success managers, and premium channels like Slack integration. Growth customers might get standard branding with some customization, friendly educational messaging, priority support queues, and email plus in-app notifications. Trial users often receive default branding, value-focused urgent messaging, self-service support, and email-only communications.
| Use Case | Implementation | Benefit |
|---|---|---|
| White-label | Custom brand per customer | Feels native to their product |
| Tier-based | Brand per pricing tier | Differentiated experience |
| Regional | Brand per geography | Localized messaging |
| Industry | Brand per vertical | Relevant examples/terms |
Start simple - maybe just enterprise vs. everyone else. You can always add more granularity as you grow.
See the tenant documentation for advanced patterns.
Instead of stitching together data from multiple tools, Courier provides comprehensive analytics for your notification infrastructure. You get message performance, channel effectiveness, and provider reliability all in one place - while your product analytics handle the broader user journey.

Message and Channel Performance Every message is automatically tracked across all channels - deliveries, opens, clicks, and bounces. You'll see which templates work best, how each channel performs (email open rates, push engagement, SMS delivery), and where issues occur. Real-time monitoring shows delivery rates and system health at a glance, critical for catching onboarding failures before they impact activation.
Provider Monitoring Track provider reliability to know when SendGrid is having issues or when backup providers are needed. The platform shows performance trends over time, filtered by message type or user segment.
Integration with Your Analytics Stack Courier can send engagement events (opens, clicks) back to tools like Segment, connecting message performance to your broader user activation metrics. This gives you the complete picture of how onboarding messages impact retention.
Key metrics to watch:
Remember: Courier handles notification metrics while your product analytics track the full user journey. Together, they show you exactly where to optimize.
See the analytics documentation for more details on available metrics and reporting features.
Courier provides a complete test environment where you can validate your entire onboarding flow before going live. Here's how to test each component effectively:
To test your Segment-triggered automations:
Send test events from Segment:
name, company, plan, etc.)Use Courier's test event builder:
Test different user paths:
Running test automations:
Testing Slack escalation:
Verify routing rules:
Test provider failover:
Web Inbox testing:
Mobile testing:
Preview across channels:
After running your tests, verify everything in Courier's analytics:
Check delivery metrics:
Monitor engagement:
Review automation performance:
Provider health check:
Before going live, ensure:
You now have a sophisticated onboarding system that would typically take months to build from scratch. Your system:
More importantly, you've built this in a way that's maintainable and extensible. Your product team can now iterate on flows through Courier's visual tools. Your success team gets actionable alerts. Your mobile team has native experiences that just work.
The real value is in what Courier handles for you:
This lets you focus on what actually matters: helping your users succeed with your product.
Start simple and iterate:
Remember: Great onboarding is iterative. Start with something good, measure what works, and keep improving. Your users (and your metrics) will thank you.
© 2025 Courier. All rights reserved.