BackApril 2026

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.

lib/notifykit.ts
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-app

A Next.js app with an inbox, a preferences page, and a send button. See the Quickstart.

How 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.

app/actions.ts
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:

InboxRey mentioned youStored in your database; the bell's unread count goes up live
EmailRey mentioned you in Launch PlanSent through Resend or SMTP, with a signed unsubscribe link
PreferencesComment mentions: onEach recipient chooses per notification and per channel
Digest5 comments, one emailNoisy notifications can be batched into a single message

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.

StageWhat it does
1. ValidateLooks up the recipient and checks the payload against the schema; either failing throws
2. IdempotencyReturns the earlier result if this idempotency key was already used
3. DeduplicationDrops a repeat of the same event inside its window
4. PreferencesRemoves channels the recipient turned off; stops if none are left
5. Rate limitStops the send if the recipient is over the limit for the window
6. DigestHolds the send in a batch that goes out once the window closes
7. Quiet hoursHolds email, SMS, and webhooks until quiet hours end; the inbox is written now
8. DeliverWrites the inbox item and hands each message to its provider
9. Retry and fall backRetries 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:

NotifyKitManaged platformsSelf-hosted platforms
Runs inYour app or workerThe vendor's infrastructureA separate stack you operate
State lives inYour application databaseThe vendorThe platform's own database
Workflows areTypeScript in your repositoryEdited in a dashboardA dashboard, or code
Delivery operationsYours: queue and monitoringManaged for youYours: 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:

PackageAdd it for
@notifykitjs/coreThe engine: definitions, sending, preferences, the HTTP handler, memory adapter, and fake providers
@notifykitjs/drizzleKeeping state in SQLite or Postgres
@notifykitjs/nextNext.js route handlers, server actions, and middleware
@notifykitjs/reactInbox and preference hooks and components
@notifykitjs/resendSending email through Resend
@notifykitjs/nodemailerSending email through any SMTP server (not on npm yet)
@notifykitjs/realtime-ws, realtime-pgLive inbox events for your own WebSocket clients, or shared across servers through Postgres
@notifykitjs/testingA test harness with fake providers and assertions
@notifykitjs/cliChecking your notification definitions in CI
create-notifykit-appStarting a new Next.js app

Status

Next Steps