Notifications
In-app and email notifications with templates, real-time streaming, and user preferences.
By the end of this guide, you'll understand how to implement a comprehensive notification system that delivers in-app and email notifications with real-time streaming, user preferences, and customizable templates.
Overview
The SaaS Boilerplate includes a powerful notification system that supports multiple delivery channels and provides real-time updates. Key features include:
- Multi-channel delivery: In-app notifications and email delivery
- Real-time streaming: Automatic UI updates when new notifications arrive
- User preferences: Granular control over notification types and channels
- Template system: Predefined notification types with dynamic content
- Organization scoping: Proper data isolation for multi-tenant applications
- Read status tracking: Mark notifications as read with bulk operations
The system integrates deeply with authentication and automatically includes notification data in user sessions for seamless access control.
Architecture
Notification Service
The core of the notification system is the NotificationService class, which orchestrates all notification operations:
// src/services/notification.ts
const notification = new NotificationService({
context: {} as NotificationContext,
channels: {
'email': { /* email delivery logic */ },
'in-app': { /* database storage logic */ }
},
templates: {
USER_INVITED: { /* template definition */ },
LEAD_CREATED: { /* template definition */ },
// ... more templates
}
})Channels
Notifications can be delivered through multiple channels:
- Email: Uses the mail service to send HTML emails with templates
- In-app: Stores notifications in the database for UI display
Templates
Each notification type has a predefined template with:
- Zod schema for data validation
- Dynamic title and description functions
- Action buttons with URLs
- Supported channels
- Help text for user preferences
Setting Up Notifications
Configure Notification Service
The notification service is automatically configured in src/services/notification.ts with channels and templates. It's available in the Igniter context at context.services.notification.
Use Notification Controller
The notification controller provides REST endpoints for managing notifications. Enable real-time streaming by setting stream: true in query definitions.
Configure User Preferences
Users can customize notification delivery preferences through the API endpoints. Preferences are stored in user metadata.
Notification Templates
The system includes predefined templates for common events:
Prop
Type
Template Structure
Each template defines:
USER_INVITED: {
channels: ['email', 'in-app'],
title: 'User Invited',
description: 'You have been invited to join an organization',
help: 'When a user is invited to an organization',
action: {
label: 'Accept',
url: '/app/invites'
},
schema: z.object({
organizationName: z.string(),
inviterName: z.string(),
role: z.string()
})
}Backend Usage (Procedures & Controllers)
Sending Notifications
Trigger notifications from your backend business logic:
// In a procedure or controller
await context.services.notification.send({
type: 'USER_INVITED',
data: {
organizationName: organization.name,
inviterName: session.user.name,
role: invitation.role
},
context: {
recipientId: invitation.email, // Send to specific user
organizationId: organization.id
}
})Organization-wide Notifications
Send notifications to all members of an organization:
// Notify all organization members
await context.services.notification.send({
type: 'LEAD_CREATED',
data: {
leadName: lead.name,
leadEmail: lead.email,
source: 'website'
},
context: {
organizationId: session.organization.id // Notify all members
}
})Error Handling
Always wrap notification sending in error handling:
// Graceful error handling
try {
await context.services.notification.send({
type: 'BILLING_SUCCESS',
data: { amount: 29.99, currency: 'USD', planName: 'Pro' },
context: { recipientId: userId, organizationId: orgId }
})
} catch (error) {
// Log error but don't break main flow
console.error('Notification failed:', error)
}Frontend Usage (Client-side)
Listing Notifications
Retrieve paginated notifications with filtering:
// Get first page of unread notifications
const result = await api.notification.list.query({
limit: 20,
page: 1,
unreadOnly: true
})
// Access pagination data
const { notifications, pagination } = result.data
console.log(`Page ${pagination.page} of ${pagination.totalPages}`)Read Status Management
Mark notifications as read individually or in bulk:
// Mark specific notification as read
await api.notification.markAsRead.mutate({
id: 'notif_123'
})
// Mark all notifications as read
await api.notification.markAllAsRead.mutate()
// Returns: { updatedCount: 5, message: "5 notifications marked as read" }Unread Count
Get count of unread notifications for UI badges:
// Get unread count
const { count } = await api.notification.unreadCount.query()
// Update UI badge
updateNotificationBadge(count)User Preferences
Manage notification delivery preferences:
// Get user preferences
const { types } = await api.notification.getUserPreferences.query()
// Update preferences
await api.notification.updateNotificationPreferences.mutate({
preferences: {
USER_INVITED: { inApp: true, email: false },
LEAD_CREATED: { inApp: true, email: true }
}
})Real-time Streaming
Automatic Updates
Notifications support real-time streaming for instant UI updates:
// In a React component
function NotificationList() {
const { data } = api.notification.list.useQuery({
limit: 20,
unreadOnly: true
})
// Component automatically re-renders when new notifications arrive
return (
<div>
{data?.notifications.map(notification => (
<NotificationItem key={notification.id} {...notification} />
))}
</div>
)
}Streaming Configuration
Enable streaming in query definitions:
list: igniter.query({
path: '/',
stream: true, // Enables real-time updates
use: [AuthFeatureProcedure(), NotificationProcedure()],
handler: async ({ context, response }) => {
// Handler returns data that gets streamed to clients
}
})Practical Examples
Backend: Lead Creation Notifications
Automatically notify team members when new leads are created:
// In lead creation controller/procedure
export const createLead = igniter.mutation({
// ... other config
handler: async ({ context, request, response }) => {
const session = await context.auth.getSession({
requirements: 'authenticated'
})
// Create the lead
const lead = await context.database.lead.create({
data: request.body
})
// Send notification to all organization members
await context.services.notification.send({
type: 'LEAD_CREATED',
data: {
leadName: lead.name,
leadEmail: lead.email,
source: 'website'
},
context: {
organizationId: session.organization.id
}
})
return response.created(lead)
}
})Frontend: Notification Center Component
Build a complete notification center:
function NotificationCenter() {
const [unreadCount, setUnreadCount] = useState(0)
const { data: notifications } = api.notification.list.useQuery({
limit: 50,
unreadOnly: false
})
// Update badge count
useEffect(() => {
api.notification.unreadCount.query().then(result => {
setUnreadCount(result.data.count)
})
}, [])
return (
<div className="notification-center">
<div className="header">
<h3>Notifications</h3>
<Badge count={unreadCount} />
</div>
<div className="notifications">
{notifications?.data.notifications.map(notification => (
<NotificationItem
key={notification.id}
notification={notification}
onMarkAsRead={() => api.notification.markAsRead.mutate({
id: notification.id
})}
/>
))}
</div>
</div>
)
}Notification Data Structure
Notification Object
Prop
Type
Troubleshooting
Best Practices
See Also
- Authentication & Sessions - How notifications integrate with user sessions
- Email - Email delivery system used by notifications
- Organizations and Tenancy - Organization-scoped notifications
- Jobs & Queues - Background processing for bulk notifications
- Content Layer - MDX content system for notification templates
API Reference
Notification Endpoints
Prop
Type