NotifyKit
App-native notifications for TypeScript. Define them in code, keep their state in your own database, and ship an inbox, email, preferences, and unsubscribes without running a notification platform.
import {
channel,
createNotifyKit,
memoryAdapter,
notification,
} from "@notifykitjs/core"
import { resendProvider } from "@notifykitjs/resend"
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: memoryAdapter(),
providers: {
email: resendProvider({
apiKey: process.env.RESEND_API_KEY!,
from: "hi@example.com",
}),
},
})Works With
- TypeScript on Node.js 18+
- Next.js 14 and later
- React 18 and 19
- SQLite and Postgres, via Drizzle
- Resend, or any SMTP server
- Zod, Valibot, and ArkType
- Your own queue and providers
Overview
NotifyKit is a notification framework that lives inside your app. Notifications are TypeScript definitions next to the code that sends them, so a wrong id or a missing payload field is a compile error. Inbox items, preferences, and the delivery log are rows in your own database, and delivery runs in your own process or worker. There is no dashboard to keep in sync with your deploys and no outside service holding your users’ notification history.
Start Here
Install
Start a new Next.js app with everything wired up, or add the core package to an app you already have. The core has no required dependencies: it ships an in-memory database and fake providers, so it runs before you have picked either.
npx create-notifykit-app my-appA Next.js app with an inbox, a preferences page, and a send button. See the Quickstart.
npm install @notifykitjs/corebun add @notifykitjs/corepnpm add @notifykitjs/coreHow It Works
Define a notification once: an id, the payload it needs, and a template for each channel it can go out on. Send it from anywhere on your server with notify.send(), naming a recipient and passing the payload. NotifyKit then delivers it: an inbox item written to your database, an email through your provider, a signed webhook, each one checked against the recipient’s preferences first.
await notify.send({
recipientId: "user_123",
notificationId: "comment_mentioned",
payload: { actorName: "Rey", postTitle: "Launch Plan", postUrl: "/posts/42" },
})One call, several places it can land:
On the client, @notifykitjs/react gives you useInbox(), usePreferences(), and a <NotificationBell />, talking to routes you mount with one line in Next.js.
The Pipeline
Every send() goes through the same stages, in this order. Each one you have not configured is a no-op, and the first one that stops a send is the one to look at when a message does not arrive.
| Stage | What it does |
|---|---|
| 1. Validate | Looks up the recipient and checks the payload against the schema; either failing throws |
| 2. Idempotency | Returns the earlier result if this idempotency key was already used |
| 3. Deduplication | Drops a repeat of the same event inside its window |
| 4. Preferences | Removes channels the recipient turned off; stops if none are left |
| 5. Rate limit | Stops the send if the recipient is over the limit for the window |
| 6. Digest | Holds the send in a batch that goes out once the window closes |
| 7. Quiet hours | Holds email, SMS, and webhooks until quiet hours end; the inbox is written now |
| 8. Deliver | Writes the inbox item and hands each message to its provider |
| 9. Retry and fall back | Retries failures with backoff, then tries a fallback channel |
notify.explain() runs the same stages without storing or sending anything and tells you what each channel would do. See Explain and dry run.
Your Database
Recipients, inbox items, preferences, delivery records, and the delivery timeline are stored through a database adapter. memoryAdapter() is built in for development and tests; @notifykitjs/drizzle stores everything in SQLite or Postgres tables that sit next to your own, so you can join against them, back them up, and migrate them like the rest of your schema.
Nothing leaves your infrastructure except the messages themselves, which go to the email, SMS, or webhook provider you configured. See Database adapters.
Compared
NotifyKit is a library, not a platform, and that is a trade-off rather than an upgrade:
| NotifyKit | Managed platforms | Self-hosted platforms | |
|---|---|---|---|
| Runs in | Your app or worker | The vendor's infrastructure | A separate stack you operate |
| State lives in | Your application database | The vendor | The platform's own database |
| Workflows are | TypeScript in your repository | Edited in a dashboard | A dashboard, or code |
| Delivery operations | Yours: queue and monitoring | Managed for you | Yours: the platform's workers |
Choose a managed platform such as Knock, Courier, SuprSend, or Novu Cloud when non-engineers need to edit workflows, or you want many channels and managed delivery out of the box. Why NotifyKit goes through the choice in more detail.
Packages
Only the core is required. Add the rest as you need them:
| Package | Add it for |
|---|---|
@notifykitjs/core | The engine: definitions, sending, preferences, the HTTP handler, memory adapter, and fake providers |
@notifykitjs/drizzle | Keeping state in SQLite or Postgres |
@notifykitjs/next | Next.js route handlers, server actions, and middleware |
@notifykitjs/react | Inbox and preference hooks and components |
@notifykitjs/resend | Sending email through Resend |
@notifykitjs/nodemailer | Sending email through any SMTP server (not on npm yet) |
@notifykitjs/realtime-ws, realtime-pg | Live inbox events for your own WebSocket clients, or shared across servers through Postgres |
@notifykitjs/testing | A test harness with fake providers and assertions |
@notifykitjs/cli | Checking your notification definitions in CI |
create-notifykit-app | Starting a new Next.js app |
Status
Next Steps
- Quickstart: scaffold an app and send your first notification
- Defining notifications: payloads, templates, and categories
- Next.js and React: put an inbox and a preferences page in your app
- Explain and dry run: find out why a notification did not arrive
- API reference: every method on
notify