Notifications
Real-time notification system powered by Novu
Overview
The notification system integrates Novu, an open-source notification infrastructure, to deliver real-time in-app notifications to users. The system automatically syncs with Clerk authentication and matches the Material-UI theme for a seamless user experience.
Architecture
The notification system consists of a single client component that renders a notification bell in the app header. When clicked, it opens an inbox dropdown showing all user notifications with read/unread tracking.
File Structure
Configuration
Environment Variables
Add your Novu application identifier to .env.local:
NEXT_PUBLIC_NOVU_APPLICATION_IDENTIFIER=your-app-identifierGet this identifier from Novu Dashboard → Settings → API Keys.
The NEXT_PUBLIC_ prefix is required because Novu renders client-side and
needs access to this value in the browser.
Implementation
Notification Inbox Component
The inbox component integrates Clerk authentication with Novu's notification system. It automatically creates or updates the subscriber profile whenever the user logs in.
"use client"
import { useUser } from "@clerk/nextjs"
import { Inbox } from "@novu/nextjs"
import { Bell } from "@phosphor-icons/react"
export function NotificationInbox() {
const { user, isLoaded } = useUser()
const applicationIdentifier = process.env.NEXT_PUBLIC_NOVU_APPLICATION_IDENTIFIER
if (!applicationIdentifier) {
console.error("NEXT_PUBLIC_NOVU_APPLICATION_IDENTIFIER is not configured")
return null
}
if (!isLoaded || !user) {
return null
}
return (
<Inbox
applicationIdentifier={applicationIdentifier}
subscriber={{
subscriberId: user.id,
firstName: user.firstName ?? undefined,
lastName: user.lastName ?? undefined,
email: user.primaryEmailAddress?.emailAddress,
avatar: user.imageUrl,
}}
appearance={{
variables: {
colorPrimary: "var(--mui-palette-primary-main)",
colorPrimaryForeground: "var(--mui-palette-primary-contrastText)",
colorSecondary: "var(--mui-palette-secondary-main)",
colorSecondaryForeground: "var(--mui-palette-secondary-contrastText)",
colorBackground: "var(--mui-palette-background-paper)",
colorForeground: "var(--mui-palette-text-primary)",
colorNeutral: "var(--mui-palette-divider)",
fontSize: "14px",
},
icons: {
bell: () => <Bell size={24} />,
},
}}
/>
)
}Key Implementation Details
Layout Integration
The notification inbox is placed in the app header, next to the user menu:
import { NotificationInbox } from "@/components/notifications/notification-inbox"
export function VerticalLayout({ children }) {
return (
<Box>
<AppBar>
<Toolbar>
<Logo />
<Navigation />
<Box sx={{ ml: "auto", display: "flex", gap: 2 }}>
<NotificationInbox />
<UserButton />
</Toolbar>
</AppBar>
<Main>{children}</Main>
</AppBar>
</Box>
)
}Backend Integration
Creating Workflows
Workflows define the notification templates and delivery channels. Create them in the Novu Dashboard:
Navigate to Workflows
Go to Novu Dashboard → Workflows → Create Workflow
Configure Trigger
Set a unique trigger ID (e.g., guest-disruption-alert) that your backend will use to send notifications
Select Channels
Choose delivery channels: In-app (required), Email, SMS, Push (optional)
Design Template
Create the notification content using Handlebars syntax with payload variables
Sending Notifications
From your backend API, trigger notifications using the Novu Node.js SDK:
import { Novu } from "@novu/node"
const novu = new Novu(process.env.NOVU_API_KEY)
await novu.trigger("guest-disruption-alert", {
to: {
subscriberId: userId,
},
payload: {
title: "Flight Schedule Changed",
message: "Your flight LAX-JFK on 2024-01-15 has been rescheduled",
route: "LAX-JFK",
flightNumber: "OD123",
link: "/connections/guest-disruptions",
},
})Notification Types
Notify users about schedule upload status and processing results:
await novu.trigger("schedule-update", {
to: { subscriberId: userId },
payload: {
title: "Schedule Updated",
message: "New schedule uploaded successfully",
scheduleId: "SCH456",
timestamp: new Date().toISOString(),
link: "/admin/schedules",
},
})Use case: Confirm successful schedule uploads or alert about processing errors.
Notify users about system-wide events or maintenance:
await novu.trigger("system-alert", {
to: { subscriberId: userId },
payload: {
title: "System Maintenance",
message: "Scheduled maintenance on Jan 20, 2024 at 2:00 AM UTC",
severity: "warning",
link: "/settings",
},
})Use case: Communicate planned downtime or important system updates.
Styling & Theming
Material-UI Integration
The inbox automatically inherits your Material-UI theme through CSS variables. This approach ensures the notification UI stays consistent with the rest of the application, including automatic light/dark mode support.
appearance={{
variables: {
colorPrimary: "var(--mui-palette-primary-main)",
colorBackground: "var(--mui-palette-background-paper)",
colorForeground: "var(--mui-palette-text-primary)",
colorNeutral: "var(--mui-palette-divider)",
fontSize: "14px",
},
}}Why CSS variables? Material-UI exposes its theme as CSS variables, allowing Novu to reactively update when the theme changes (e.g., switching to dark mode).
Custom Styling
For advanced customization, target Novu's data attributes in your global CSS:
[data-novu-component="inbox"] {
/* Customize inbox container */
max-width: 400px;
}
[data-novu-component="notification-item"] {
/* Customize individual notifications */
border-radius: 8px;
}
[data-novu-component="notification-item"][data-read="false"] {
/* Style unread notifications */
background-color: var(--mui-palette-action-hover);
}Real-Time Updates
Novu uses WebSockets to deliver notifications in real-time. When a notification is triggered from the backend, it appears instantly in the user's inbox without requiring a page refresh or polling.
How It Works
The WebSocket connection is established automatically when the <Inbox> component mounts. No additional configuration is needed.
Testing
Local Development
Test notifications locally by creating a test endpoint:
import { Novu } from "@novu/node"
export async function POST(request: Request) {
const { userId, type } = await request.json()
const novu = new Novu(process.env.NOVU_API_KEY)
await novu.trigger(type, {
to: { subscriberId: userId },
payload: {
title: "Test Notification",
message: "This is a test notification",
link: "/",
},
})
return Response.json({ success: true })
}Novu Dashboard Testing
Open Workflow
Go to Workflows → Select your workflow
Click Test Workflow
Enter test payload data
Send Test
Click "Send Test" button
Check Inbox
Open your app and verify the notification appears
Monitoring
Analytics
Track notification performance in the Novu Dashboard:
| Metric | Description | Why It Matters |
|---|---|---|
| Delivery Rate | % successfully delivered | Identifies delivery issues |
| Open Rate | % of notifications opened | Measures user engagement |
| Click-Through Rate | % of links clicked | Tracks notification effectiveness |
| Errors | Failed deliveries | Helps debug issues |
Activity Feed
The Activity Feed shows real-time notification events. Filter by subscriber, workflow, status, or date range to debug issues or analyze user behavior.
Best Practices
Troubleshooting
Advanced Features
Custom Notification Actions
Add custom action buttons to notifications:
<Inbox
applicationIdentifier={applicationIdentifier}
subscriber={subscriber}
notificationActions={(notification) => [
{
label: "View Details",
onClick: () => router.push(notification.data.link),
},
{
label: "Dismiss",
onClick: () => markAsRead(notification.id),
},
]}
/>Multi-Tenant Support
For applications with multiple organizations, include the organization ID in the subscriber ID:
subscriber={{
subscriberId: `${organizationId}:${userId}`,
data: {
organizationId: organizationId,
},
}}This allows you to send notifications to all users in an organization or filter by organization in the backend.
Next: Explore the Deployment Guide to learn about CI/CD pipelines and deployment strategies.