Users expect in-app notifications. The little bell icon with an unread count. A dropdown showing recent activity. The ability to mark items as read. It sounds simple, but building it from scratch means dealing with real-time delivery, read state management, pagination, and UI edge cases.
Zyphr's React inbox component handles all of this out of the box. Here's how to add it to your app.
Prerequisites
Step 1: Install the package
npm install @zyphr/inbox-react
Step 2: Add the provider
Wrap your app (or the section that needs inbox access) with the ZyphrInboxProvider:
import { ZyphrInboxProvider } from '@zyphr/inbox-react';
function App() {
return (
<ZyphrInboxProvider
subscriberId="user_abc123"
apiKey={process.env.NEXT_PUBLIC_ZYPHR_CLIENT_KEY}
>
<YourApp />
</ZyphrInboxProvider>
);
}
The provider establishes a real-time connection and manages notification state. Notifications appear instantly — no polling.
Step 3: Render the inbox
Drop in the pre-built inbox component:
import { InboxBell } from '@zyphr/inbox-react';
function Header() {
return (
<nav>
<h1>YourApp</h1>
<InboxBell />
</nav>
);
}
That's it. The InboxBell renders a bell icon with an unread count badge. Clicking it opens a dropdown with the notification list. Each item shows the notification content, timestamp, and read/unread state.
Step 4: Send a notification
From your backend, send an in-app notification using the SDK:
await zyphr.inApp.send({
subscriberId: 'user_abc123',
template: 'new-comment',
variables: {
commenterName: 'Sarah',
postTitle: 'Introducing Zyphr',
commentPreview: 'This looks great! When is the...',
},
});
The notification appears in the user's inbox in real time. No page refresh needed.
Customizing the inbox
Custom bell icon
Don't like the default bell? Render your own:
import { useUnreadCount } from '@zyphr/inbox-react';
function CustomBell() {
const { count } = useUnreadCount();
return (
<button className="notification-btn">
<BellIcon />
{count > 0 && <span className="badge">{count}</span>}
</button>
);
}
Custom notification list
For full control over the notification UI, use the useInbox hook:
import { useInbox } from '@zyphr/inbox-react';
function NotificationList() {
const { notifications, markAsRead, markAllAsRead, isLoading } = useInbox();
if (isLoading) return <Spinner />;
return (
<div>
<button onClick={markAllAsRead}>Mark all as read</button>
{notifications.map((notification) => (
<div
key={notification.id}
className={notification.read ? 'read' : 'unread'}
onClick={() => markAsRead(notification.id)}
>
<p>{notification.body}</p>
<time>{formatRelativeTime(notification.createdAt)}</time>
</div>
))}
</div>
);
}
Theming
The default inbox component accepts a theme prop for visual customization:
<InboxBell
theme={{
colors: {
primary: '#6C5CE7',
unread: '#FFF3E0',
},
borderRadius: '8px',
fontSize: '14px',
}}
/>
Or skip the built-in styles entirely and use the hooks to build a fully custom UI that matches your design system.
Handling notification actions
Notifications can include action URLs. When a user clicks a notification, you can navigate them to the relevant page:
import { useInbox } from '@zyphr/inbox-react';
import { useNavigate } from 'react-router-dom';
function NotificationList() {
const { notifications, markAsRead } = useInbox();
const navigate = useNavigate();
const handleClick = (notification) => {
markAsRead(notification.id);
if (notification.actionUrl) {
navigate(notification.actionUrl);
}
};
return (
<div>
{notifications.map((n) => (
<div key={n.id} onClick={() => handleClick(n)}>
{n.body}
</div>
))}
</div>
);
}
Set the actionUrl when sending the notification:
await zyphr.inApp.send({
subscriberId: 'user_abc123',
body: 'Sarah commented on your post',
actionUrl: '/posts/123#comments',
});
Multi-channel with in-app
The real power of in-app notifications comes when you combine them with other channels. Send the same notification via in-app and email, and let the user's preferences decide which channels they receive:
// Send via in-app inbox
await zyphr.inApp.send({
subscriberId: 'user_abc123',
template: 'new-comment',
variables: { commenterName: 'Sarah', postTitle: 'Intro' },
});
// Send via email (if user has email enabled for this topic)
await zyphr.emails.send({
to: 'user@example.com',
template: 'new-comment-email',
variables: { commenterName: 'Sarah', postTitle: 'Intro' },
});
Both messages appear in the unified delivery timeline in the Zyphr dashboard.
Next steps
The full inbox component documentation covers pagination, custom sorting, notification grouping, and more.
Read the inbox docs →