| name | stacks-notifications |
| description | Use when implementing notifications in Stacks — multi-channel notifications (email, SMS, push, chat, database), the database notification driver with read/unread tracking, notification factories (useEmail, useSMS, useChat, useDatabase), or notification configuration. Covers @stacksjs/notifications and config/notification.ts. |
| license | MIT |
| compatibility | Bun >= 1.3.0, TypeScript |
| allowed-tools | Read Edit Write Bash Grep Glob |
Stacks Notifications
Multi-channel notification system with 5 channel types: email, SMS, chat, push, and database.
Key Paths
- Core package:
storage/framework/core/notifications/src/
- Main entry:
storage/framework/core/notifications/src/index.ts
- Drivers:
storage/framework/core/notifications/src/drivers/
- Database driver:
storage/framework/core/notifications/src/drivers/database.ts
- Configuration:
config/notification.ts
- Notification model:
storage/framework/defaults/app/Models/Notification.ts
Package Exports
import {
useChat,
useEmail,
useSMS,
useDatabase,
useNotification,
notification,
DatabaseNotificationDriver,
} from '@stacksjs/notifications'
import type { CreateNotificationOptions, DatabaseNotification } from '@stacksjs/notifications'
Channel Factories
Each factory returns the underlying driver module for that channel.
const emailDriver = useEmail('ses')
const emailDriver = useEmail('sendgrid')
const emailDriver = useEmail('mailgun')
const emailDriver = useEmail('mailtrap')
const emailDriver = useEmail('smtp')
const smsDriver = useSMS('twilio')
const smsDriver = useSMS('vonage')
const chatDriver = useChat('slack')
const chatDriver = useChat('discord')
const chatDriver = useChat('teams')
const dbDriver = useDatabase()
const driver = useNotification('email', 'ses')
const driver = useNotification('sms', 'twilio')
const driver = useNotification(, )
driver = ()
driver = ()
useNotification() reads config/notification.ts for the default type. If no default is set, it throws 'No default notification type set in config/notification.ts'.
The channel drivers are re-exports from their respective packages:
email driver: @stacksjs/email
sms driver: @stacksjs/sms
chat driver: @stacksjs/chat
push driver: @stacksjs/push
Database Notification Driver
The DatabaseNotificationDriver provides CRUD operations for notifications stored in the notifications database table using Kysely query builder.
Send a Notification
const db = useDatabase()
const notification = await db.send({
userId: 1,
type: 'order.shipped',
data: { orderId: 42, trackingNumber: 'ABC123' },
})
send() inserts a row into the notifications table with:
user_id from options.userId
type from options.type
data -- JSON.stringify'd from options.data
read_at set to null
created_at and updated_at set to current ISO timestamp
Query Notifications
const all = await db.getUserNotifications(userId)
const unread = await db.getUnreadNotifications(userId)
const count = await db.unreadCount(userId)
Mark as Read
await db.markAsRead(notificationId)
await db.markAllAsRead(userId)
markAllAsRead() only updates rows where read_at is null.
Delete Notifications
await db.deleteNotification(notificationId)
await db.deleteAllNotifications(userId)
DatabaseNotification Interface
interface DatabaseNotification {
id: number
user_id: number
type: string
data: string
read_at: string | null
created_at: string
updated_at: string | null
}
CreateNotificationOptions Interface
interface CreateNotificationOptions {
userId: number
type: string
data: Record<string, any>
}
Notification Model Fields
The Notification model at storage/framework/defaults/app/Models/Notification.ts
maps the database notification inbox used by DatabaseNotificationDriver:
user_id comes from the belongsTo: ['User'] relationship
type is an application event name such as order.shipped
data is a JSON string containing the notification payload
readAt maps to the nullable read_at column
created_at and updated_at come from useTimestamps
The model uses useApi for its CRUD API and seeds 30 records by default. It
must stay aligned with notificationsTableSql() in
storage/framework/core/database/src/notification-tables.ts. Outbound
transport attempts belong in a separate delivery-log model and table. Do not
add email, SMS, or provider-specific delivery columns to the inbox model.
CLI Commands
buddy make:notification [name] -- scaffold a new notification
config/notification.ts
import type { NotificationConfig } from '@stacksjs/types'
export default {
default: 'email',
} satisfies NotificationConfig
The default field controls which channel type useNotification() and notification() use when no type is specified. Valid values: 'email', 'sms', 'chat', 'database'.
Architecture
The notifications package is a thin aggregation layer. Each channel delegates to its own dedicated package:
- Email channel (
useEmail) -- re-exports @stacksjs/email (configured via config/email.ts)
- SMS channel (
useSMS) -- re-exports @stacksjs/sms (configured via config/sms.ts)
- Chat channel (
useChat) -- re-exports @stacksjs/chat (configured via config/services.ts)
- Push channel -- re-exports
@stacksjs/push
- Database channel (
useDatabase) -- built-in driver using @stacksjs/database
The driver modules (drivers/email.ts, drivers/sms.ts, etc.) are single-line re-exports: export * as email from '@stacksjs/email', export * as sms from '@stacksjs/sms', etc.
Gotchas
- The
data field in database notifications is stored as a JSON string -- always JSON.parse() when reading
read_at is null for unread notifications -- use this to filter unread
- The database driver uses Kysely's query builder with
as any type casts on table/column names since the notifications table is dynamically referenced
- Channel-specific configuration (SMTP credentials, Twilio keys, Slack tokens) lives in each channel's own config file, not in
config/notification.ts
useNotification() throws if config.default is not set in config/notification.ts
- The
notification() function (without arguments) is a shorthand for useNotification() with defaults
- The
nexmo driver is a legacy alias for vonage in the SMS drivers index