stacks-notifications

$npx mdskill add stacksjs/stacks/stacks-notifications

Send multi-channel notifications via email, SMS, chat, push, or database.

  • Supports five notification channels: email, SMS, chat, push, and database.
  • Depends on @stacksjs/notifications package and config/notification.ts.
  • Selects channel based on factory functions like useEmail or useSMS.
  • Returns driver instances for sending and tracking notifications.

SKILL.md

.github/skills/stacks-notificationsView on GitHub ↗
---
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/models/Notification.ts`

## Package Exports

```typescript
import {
  useChat,
  useEmail,
  useSMS,
  useDatabase,
  useNotification,
  notification,
  DatabaseNotificationDriver,
} from '@stacksjs/notifications'

// Types
import type { CreateNotificationOptions, DatabaseNotification } from '@stacksjs/notifications'
```

## Channel Factories

Each factory returns the underlying driver module for that channel.

```typescript
// Email -- defaults to 'mailtrap', returns @stacksjs/email driver
const emailDriver = useEmail('ses')
const emailDriver = useEmail('sendgrid')
const emailDriver = useEmail('mailgun')
const emailDriver = useEmail('mailtrap')
const emailDriver = useEmail('smtp')

// SMS -- defaults to 'twilio', returns @stacksjs/sms driver
const smsDriver = useSMS('twilio')
const smsDriver = useSMS('vonage')

// Chat -- defaults to 'slack', returns @stacksjs/chat driver
const chatDriver = useChat('slack')
const chatDriver = useChat('discord')
const chatDriver = useChat('teams')

// Database -- returns DatabaseNotificationDriver object
const dbDriver = useDatabase()

// Auto-detect by type -- dispatches to the correct factory
const driver = useNotification('email', 'ses')
const driver = useNotification('sms', 'twilio')
const driver = useNotification('chat', 'slack')
const driver = useNotification('database')

// Default (uses config.default, falls back to 'email' + 'mailtrap')
const driver = notification()
```

`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

```typescript
const db = useDatabase()

const notification = await db.send({
  userId: 1,
  type: 'order.shipped',
  data: { orderId: 42, trackingNumber: 'ABC123' },
})
// Returns the created DatabaseNotification with auto-generated id, timestamps
```

`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

```typescript
// All notifications for a user, ordered by created_at desc
const all = await db.getUserNotifications(userId)

// Only unread (where read_at is null), ordered by created_at desc
const unread = await db.getUnreadNotifications(userId)

// Count of unread notifications
const count = await db.unreadCount(userId)
```

### Mark as Read

```typescript
// Mark a single notification as read (sets read_at to current timestamp)
await db.markAsRead(notificationId)

// Mark all unread notifications for a user as read
await db.markAllAsRead(userId)
```

`markAllAsRead()` only updates rows where `read_at` is null.

### Delete Notifications

```typescript
// Delete a single notification by ID
await db.deleteNotification(notificationId)

// Delete all notifications for a user
await db.deleteAllNotifications(userId)
```

### DatabaseNotification Interface

```typescript
interface DatabaseNotification {
  id: number
  user_id: number
  type: string          // e.g., 'order.shipped', 'payment.received'
  data: string          // JSON stringified -- parse with JSON.parse() when reading
  read_at: string | null  // ISO timestamp or null if unread
  created_at: string    // ISO timestamp
  updated_at: string | null  // ISO timestamp
}
```

### CreateNotificationOptions Interface

```typescript
interface CreateNotificationOptions {
  userId: number
  type: string
  data: Record<string, any>
}
```

## Notification Model Fields

The Notification model (at `storage/framework/models/Notification.ts`) defines:
- `type` -- notification type: email, sms, push, slack, webhook
- `channel` -- delivery channel
- `recipient` -- target email, phone, or user
- `subject` -- notification subject line
- `body` -- notification body content
- `status` -- pending, sent, delivered, failed, read
- `readAt`, `sentAt`, `metadata`

Note: The Notification model uses 30 seeded records by default.

## CLI Commands
- `buddy make:notification [name]` -- scaffold a new notification

## config/notification.ts

```typescript
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/chat.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

More from stacksjs/stacks

SkillDescription
stacks-actionsUse when working with Stacks server actions — creating actions in app/Actions/, auto-generated API actions from the useApi model trait, the 80+ default framework actions (auth, dashboard, commerce, content, deployment, jobs), action request/response handling, or action registration. Covers @stacksjs/actions and storage/framework/defaults/actions/.
stacks-aiUse when integrating AI capabilities into a Stacks application — using Anthropic/OpenAI/Ollama/AWS Bedrock drivers, image generation (DALL-E), vision analysis, RAG/vector search, embeddings, MCP (Model Context Protocol) clients, text summarization, sentiment analysis, content classification, personalization, or the buddy AI assistant. Covers @stacksjs/ai and config/ai.ts.
stacks-aliasUse when working with path aliases in a Stacks project — import resolution, module aliasing, or debugging import paths. Covers @stacksjs/alias which defines 260+ path mappings for the entire framework.
stacks-analyticsUse when adding analytics to a Stacks application — configuring Fathom or self-hosted analytics, generating tracking scripts, privacy-friendly analytics setup, or the analytics configuration. Covers @stacksjs/analytics and config/analytics.ts.
stacks-apiUse when building, modifying, or debugging API endpoints in a Stacks application — defining routes, handling requests, API middleware, working with the API server, HTTP client (fetcher), API resources, or OpenAPI generation. Covers both @stacksjs/api utilities and the stacks-api server implementation.
stacks-arraysUse when working with array utilities in Stacks — statistical operations (average, median, mode, standard deviation, z-score, percentile, covariance), array manipulation (unique, flatten, partition, shuffle, sample, move), containment checks, or the Arr facade. Covers @stacksjs/arrays.
stacks-authUse when implementing authentication, authorization, passkeys, TOTP/2FA, RBAC, gates, policies, session auth, token management, email verification, password resets, or rate limiting in a Stacks application. Covers the @stacksjs/auth package, config/auth.ts, app/Gates.ts, and app/Middleware/.
stacks-auto-importsUse when working with the Stacks auto-import system — understanding how browser and server auto-imports work, configuring auto-imported functions/models/composables, the auto-import manifests, type generation, or how globals are injected. Covers the auto-import pipeline at storage/framework/auto-imports/.
stacks-brandkitUse when defining or exploring a brand identity for a Stacks project and you want premium brand-kit REFERENCE IMAGES first - logo directions, palettes, typography specimens, identity boards, and application/mockup decks. Generates IMAGES ONLY, never code. Trained for minimalist, cinematic, editorial, dark-tech, luxury, cultural, security, gaming, developer-tool, and consumer-app brand systems with intentional logo concepting, sparse type, strong symbolic meaning, and disciplined palettes a coding step can build against.
stacks-browseUse for headless browser QA on Stacks applications — navigation, screenshots, responsive testing, console/network monitoring, and accessibility snapshots. Dependency-free: drives a system browser over the Chrome DevTools Protocol using only Bun (no Playwright/Puppeteer). Invoke with /stacks-browse.