App-native notifications
for TypeScript

Define notifications in code. Store state in your database. Ship inbox, email, preferences, and unsubscribes. No platform required.

A practical notification core
for TypeScript apps

Type-safe sends

Wrong notification ID or payload shape? TypeScript error at compile time.

Your database

Memory for dev, Drizzle persistence for SQLite and Postgres.

Multi-channel

Inbox, email, SMS, and webhook contracts. Resend, SMTP, and signed webhooks included.

Preferences

Per-notification granularity. HMAC-signed unsubscribe links. RFC 8058 one-click.

Digests & rate limits

Coalesce noisy sends. Hard-cap per window. Two lines of config.

React & Next.js

useInbox(), usePreferences(), route handlers, server actions. Realtime via WebSocket.

Testing & dev mode

Block real sends in development. Assert on deliveries in tests. Zero config.

Define

Declare notifications as code. Type-safe payloads, multi-channel delivery, template interpolation.

Expose

One-line API route. Inbox, preferences, and unsubscribe endpoints handled automatically.

Send

Trigger from anywhere in your backend. Channels, retries, and preferences resolve at runtime.

lib/notifykit.ts
import { channel, notification, createNotifyKit } from "@notifykitjs/core"

const inbox = channel.inbox()
const email = channel.email()

export const commentMentioned = notification({
  id: "comment_mentioned",
  payload: { actorName: "string", postTitle: "string", postUrl: "string" },
  channels: [
    inbox({
      title: "{{actorName}} mentioned you",
      body: "In {{postTitle}}",
      actionUrl: "{{postUrl}}",
    }),
    email({
      subject: "{{actorName}} mentioned you in {{postTitle}}",
      body: "Open {{postUrl}} to reply.",
    }),
  ],
})

export const notify = createNotifyKit({
  notifications: [commentMentioned] as const,
  database: drizzleSqliteAdapter(db),
  providers: { email: resendProvider({ apiKey, from }) },
})

What your users see

One send() call, multiple delivery surfaces — each respecting the recipient's preferences.

notify.sendcomment.mentionedpayload resolved once
In-app inboxRey mentioned youIn Launch Plan
Realtime unread +1
EmailRey mentioned you in Launch PlanOpen /posts/42 to reply.Signed unsubscribe included
Preferences
Comment mentions
Marketing updates
1

Define

lib/notifications.ts
export const invite = notification({
  id: "invite",
  payload: { name: "string" },
  channels: [inbox({ title: "{{name}} invited you" }),
             email({ subject: "You're invited", body: "Open the app to respond." })],
})
2

Send

app/api/invite/route.ts
await notify.send({
  recipientId: "user_123",
  notificationId: "invite",
  payload: { name: "Rey" },
})
3

Deliver

channels resolved at runtime
✓ Inbox    → stored in your database
✓ Email   → sent via Resend
SMS     → user opted out (preferences)