notifkit 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  **You shouldn't have to build a notification system.**
6
6
 
7
- Self-hosted notification infrastructure for product notifications. One API call handles email, SMS, push, and webhooks with preferences, quiet hours, retries, fallback, scheduling, workflows, and delivery logs built in.
7
+ Self-hosted notification infrastructure for product notifications. One API call handles email, SMS, push, and webhooks, with preferences, quiet hours, retries, fallback, scheduling, workflows, and delivery logs built in.
8
8
 
9
9
  [![npm version](https://img.shields.io/npm/v/notifkit.svg?style=flat-square&color=6366f1)](https://www.npmjs.com/package/notifkit) [![npm downloads](https://img.shields.io/npm/dm/notifkit.svg?style=flat-square&color=6366f1)](https://www.npmjs.com/package/notifkit) [![Coverage](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/devkitshq/notifkit/badges/coverage.json&style=flat-square)](https://github.com/devkitshq/notifkit/actions/workflows/ci.yml) [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-3178c6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Node.js](https://img.shields.io/badge/node-%3E%3D22.0.0-339933.svg?style=flat-square)](https://nodejs.org) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](./LICENSE)
10
10
 
@@ -12,9 +12,7 @@ Self-hosted notification infrastructure for product notifications. One API call
12
12
 
13
13
  </div>
14
14
 
15
- ---
16
-
17
- ### The first notification is easy
15
+ ### Sending one notification is easy
18
16
 
19
17
  ```ts
20
18
  await sendEmail({
@@ -24,13 +22,9 @@ await sendEmail({
24
22
  });
25
23
  ```
26
24
 
27
- ### Then reality hits
28
-
29
- Users opt out. People are asleep. Push tokens die. Providers throw 503s. Some channels fail and need fallback. You need timezone-aware quiet hours, future scheduling, deduplication, multi-channel templates, delivery logs, unsubscribe handling, multi-step workflows, and a dead-letter queue nobody wants to maintain.
25
+ Sending them reliably is the hard part. Users opt out. People are asleep. Push tokens die. Providers throw 503s. Channels fail and need fallback. Somewhere along the way you need timezone-aware quiet hours, future scheduling, deduplication, multi-channel templates, delivery logs, unsubscribe handling, multi-step workflows, and a dead-letter queue nobody wants to maintain.
30
26
 
31
- **notifkit is that machinery, already built.**
32
-
33
- Your app makes one typed call. notifkit handles the rest — **who gets it, which channel to use, when to send it, whether they're allowed to receive it, and what happens when delivery fails.**
27
+ notifkit is that machinery. Your app makes one typed call, and notifkit decides who gets the notification, which channel to use, when to send it, whether the user is allowed to receive it, and what to do when delivery fails.
34
28
 
35
29
  ```ts
36
30
  import { notifkit } from "notifkit";
@@ -43,15 +37,11 @@ await notifkit.notify({
43
37
  });
44
38
  ```
45
39
 
46
- > **Push first. If it fails, email.**
47
- >
48
- > Preferences, consent, quiet hours, retries, deduplication, throttling, template rendering, and delivery tracking happen behind that single call.
49
-
50
- ---
40
+ That call tries push, then email if push fails. Preferences, consent, quiet hours, retries, deduplication, throttling, template rendering, and delivery tracking all happen behind it.
51
41
 
52
- ## What actually runs
42
+ ## How it runs
53
43
 
54
- notifkit is both an **orchestration engine** and a **typed SDK**.
44
+ notifkit is an orchestration engine and a typed SDK.
55
45
 
56
46
  ```mermaid
57
47
  flowchart TD
@@ -79,7 +69,7 @@ flowchart TD
79
69
  DELIVER -.->|"delivery logs"| PG
80
70
  DELIVER -->|"Dispatch"| PROVIDERS
81
71
 
82
- PROVIDERS["Provider Transports<br/>Email: Resend, SES, Postmark · Push: Firebase (FCM)<br/>SMS: Twilio, MessageBird · Webhooks: Custom HTTP"]
72
+ PROVIDERS["Provider Transports<br/>Email: Resend · Push: Firebase (FCM) · SMS: Twilio<br/>Chat: Slack, Telegram, Discord, WhatsApp<br/>Webhooks: Custom HTTP"]
83
73
 
84
74
  classDef entry stroke:#6366f1,stroke-width:2px
85
75
  classDef store stroke:#0ea5e9,stroke-width:2px
@@ -89,99 +79,90 @@ flowchart TD
89
79
  class ENRICH,ENGINE,DELIVER,SCHED work
90
80
  ```
91
81
 
92
- - **`NotifkitServer`**: Runs the HTTP REST API router (`/v1/notify`, `/health`, `/metrics`) and the background worker pipelines (enricher, decision engine, scheduler, delivery).
93
- - **`NotifkitClient`**: The lightweight client your application uses to trigger notifications, sync templates, and manage users over HTTP.
82
+ `NotifkitServer` runs the HTTP REST API router (`/v1/notify`, `/health`, `/metrics`) and the background worker pipelines: enricher, decision engine, scheduler, and delivery. `NotifkitClient` is the lightweight client your application uses to trigger notifications, sync templates, and manage users over HTTP.
94
83
 
95
84
  ### Topologies
96
85
 
97
- - **Single Process (Monolith)**: Run the API and all workers in the same Node.js process (`services: ["all"]`). Perfect for small-to-medium apps, side projects, and staging.
98
- - **Distributed Services**: Run stateless API servers (`services: ["api"]`) behind a load balancer and scale worker pools (`services: ["enricher", "engine", "delivery", "scheduler"]`) horizontally across Redis Streams consumer groups.
99
-
100
- ---
101
-
102
- ## Agent-operable
86
+ In a single process, the API and all workers run in the same Node.js process (`services: ["all"]`), which works for small and medium apps, side projects, and staging. Distributed, you run stateless API servers (`services: ["api"]`) behind a load balancer and scale worker pools (`services: ["enricher", "engine", "delivery", "scheduler"]`) horizontally across Redis Streams consumer groups.
103
87
 
104
- https://github.com/user-attachments/assets/4dff98bb-37d3-44b4-bf46-9607c1cd89b5
105
-
106
- [▶️ Watch the AI demo](assets/ai_demo.mp4) - this is link to raw video file
107
-
108
- **notifkit isn't just an API your application can call — your AI agent can operate it directly.**
88
+ ## Quickstart
109
89
 
110
- Connect the notifkit MCP server ([`@notifkit/mcp`](./packages/mcp)) to Claude Code, Cursor, Claude Desktop, Gemini, or any MCP-compatible agent:
90
+ ### 1. Install
111
91
 
112
92
  ```bash
113
- npx -y @notifkit/mcp
93
+ npm install notifkit @notifkit/provider-resend
94
+ npm install -D tsx @testcontainers/postgresql @testcontainers/redis
114
95
  ```
115
96
 
116
- ### Ask your agent
97
+ The two `@testcontainers/*` packages are what notifkit uses to start throwaway PostgreSQL and Redis containers in development. They are imported lazily, only when the server is given neither a `databaseUrl`/`redisUrl` option nor a `DATABASE_URL`/`REDIS_URL` environment variable, so `devDependencies` is the right place for them.
117
98
 
118
- ```text
119
- You: Why didn't usr_9182 receive their password reset?
99
+ > [!WARNING]
100
+ > Those containers are for local development only. They are thrown away when the process exits, taking every user, template, delivery log, and queued notification with them. Before you deploy, point notifkit at a real PostgreSQL and Redis — set `DATABASE_URL` and `REDIS_URL` (or pass `databaseUrl` and `redisUrl`) and run with `NODE_ENV=production`, which refuses to start a container and fails loudly if either is missing.
120
101
 
121
- Agent: The notification was suppressed because usr_9182's email
122
- address has a hard-bounce suppression from yesterday.
123
- ```
102
+ ### 2. Run the engine
124
103
 
125
- Your application and your AI agents use the **same notification infrastructure**:
104
+ `server.ts` starts the API and the worker pipelines. In development it auto-starts those containers, so Docker is the only prerequisite.
126
105
 
127
- - **Send & dispatch** — Send one-off notifications or campaigns to users, lists, and segments (`send_notification`, `send_campaign`)
128
- - **Investigate & triage** — Diagnose delivery issues by inspecting message histories, provider responses, and quiet hours (`get_delivery_logs`, `get_notification`)
129
- - **Schedule & cancel** Schedule future sends and cancel pending notifications (`list_scheduled`, `cancel_notification`)
130
- - **Campaign analytics** Check delivery, open, click, bounce, and complaint metrics (`list_campaigns`, `get_campaign_stats`)
131
- - **Template management** — List, preview, and update templates with sample data (`list_templates`, `preview_template`, `upsert_template`)
132
- - **Users & preferences** — Look up users, contacts, preferences, and segment membership (`list_users`, `get_user_preferences`, `update_user_preferences`)
133
- - **Workflow operations** — Trigger workflows and inspect workflow runs (`create_workflow`, `trigger_workflow`, `get_workflow_run`)
134
- - **Suppressions & health** — Manage bounce suppressions, check system queues, and replay dead-letter messages (`list_suppressions`, `get_dead_letters`, `replay_dead_letter`)
106
+ ```ts
107
+ // server.ts
108
+ import { NotifkitServer } from "notifkit";
109
+ import { ResendTransport } from "@notifkit/provider-resend";
135
110
 
136
- ### From “write a script” to “just ask”
111
+ const server = new NotifkitServer({
112
+ services: ["all"], // API + enricher + engine + scheduler + delivery
113
+ port: 3000,
114
+ providers: [
115
+ new ResendTransport({
116
+ apiKey: process.env.RESEND_API_KEY!,
117
+ from: "notifications@yourdomain.com",
118
+ }),
119
+ ],
120
+ });
137
121
 
138
- | Without an agent | With NotifKit MCP |
139
- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
140
- | Jump into the DB to find contact info → open Twilio/Resend or write a throwaway script → format the payload → check their timezone manually → fire it off → hope it delivered | **You:** _“Send an urgent update to alex@acme.com that his package was lost in transit and support is rushing a replacement — text him if push doesn't deliver.”_<br><br>**Agent:** Looks up `alex@acme.com` → renders template → dispatches push with SMS fallback → bypasses quiet hours for urgent delivery → tracks delivery status → confirms it hit his phone |
122
+ await server.start();
123
+ console.log("notifkit listening on http://localhost:3000");
124
+ ```
141
125
 
142
- [Set up MCP](https://notifkit.dev/docs/mcp.html) · [MCP documentation](https://notifkit.dev/docs/mcp.html)
126
+ `ADMIN_API_KEY` is the root credential. It is read from the environment, it is what mints project API keys in the next step, and without it the project-management routes answer `403`. Any string works locally:
143
127
 
144
- ---
128
+ ```bash
129
+ ADMIN_API_KEY=supersecretkey RESEND_API_KEY=re_xxx npx tsx server.ts
130
+ ```
145
131
 
146
- ## AI-assisted migration
132
+ > [!WARNING]
133
+ > `supersecretkey` is a local placeholder. In production this one value can mint keys for every project, so use a long random string kept in your secret store — `openssl rand -hex 32` is enough.
147
134
 
148
- Already have notification code scattered across your application?
135
+ ### 3. Create a project and its API key
149
136
 
150
- Point your coding agent at:
137
+ Every `/v1/*` route requires a project API key, and only the admin credential can mint one, so this is the single bootstrap step between a running server and your first notification:
151
138
 
152
- ```text
153
- https://notifkit.dev/llms-full.txt
139
+ ```bash
140
+ ADMIN_API_KEY=supersecretkey npx notifkit-create-project "my-app"
154
141
  ```
155
142
 
156
- It can understand notifkit's API and help identify ad-hoc notification code in your repository and refactor it into durable notifkit calls.
157
-
158
- ---
143
+ ```
144
+ Project "my-app" created. Save the API key now — it is not recoverable.
159
145
 
160
- ## Quickstart
146
+ NOTIFKIT_PROJECT_ID=1ce67fa1-b4a9-4985-8046-ef6018912b2a
147
+ NOTIFKIT_API_KEY=nk_live_f57c57b76d795cef89e2dbf6b6f352a36…
148
+ ```
161
149
 
162
- ### 1. Install
150
+ The server stores only a SHA-256 hash of the key, so the `nk_live_…` value is printed once and never again — put it in your app's `.env` now. Point the script at another host with `NOTIFKIT_URL`, and mint further keys later with `POST /v1/projects/:id/keys` (`role: "read_only"` there gets you a key that can read but not send).
163
151
 
164
- ```bash
165
- npm install notifkit @notifkit/provider-resend
166
- ```
152
+ ### 4. Dispatch your first notification
167
153
 
168
- ### 2. Run the engine and dispatch your first notification
154
+ `client.ts` is your application code. It talks to the server over HTTP: register a template, register a user, and send.
169
155
 
170
156
  ```ts
171
- import { NotifkitServer, NotifkitClient } from "notifkit";
172
- import { ResendTransport } from "@notifkit/provider-resend";
157
+ // client.ts
158
+ import { NotifkitClient } from "notifkit";
173
159
 
174
- // 1. Start the server (runs API + workers; auto-starts Postgres & Redis in dev)
175
- const server = new NotifkitServer({
176
- services: ["all"],
177
- port: 3000,
178
- providers: [new ResendTransport({ apiKey: process.env.RESEND_API_KEY! })],
160
+ const notifkit = new NotifkitClient({
161
+ baseUrl: "http://localhost:3000",
162
+ apiKey: process.env.NOTIFKIT_API_KEY!,
179
163
  });
180
- await server.start();
181
-
182
- // 2. Instantiate client and register a template
183
- const notifkit = new NotifkitClient({ baseUrl: "http://localhost:3000" });
184
164
 
165
+ // 1. Register a template
185
166
  await notifkit.syncTemplates({
186
167
  templates: [
187
168
  {
@@ -192,9 +173,10 @@ await notifkit.syncTemplates({
192
173
  ],
193
174
  });
194
175
 
195
- // 3. Register user and dispatch
176
+ // 2. Register a user
196
177
  await notifkit.addUser({ id: "usr_123", email: "alex@acme.com" });
197
178
 
179
+ // 3. Dispatch
198
180
  await notifkit.notify({
199
181
  user: "usr_123",
200
182
  template: "order-shipped",
@@ -203,13 +185,20 @@ await notifkit.notify({
203
185
  });
204
186
  ```
205
187
 
206
- ### 3. Or call directly via REST API
188
+ With the server still running in the first terminal, run the client in a second one:
207
189
 
208
- You don't need the Node.js SDK — notifkit exposes a standard HTTP REST API, so you can dispatch notifications and manage resources from any language (cURL, Python, Go, etc.):
190
+ ```bash
191
+ NOTIFKIT_API_KEY=nk_live_xxx npx tsx client.ts
192
+ ```
193
+
194
+ ### 5. Or call the REST API directly
195
+
196
+ The Node.js SDK is optional. notifkit exposes a standard HTTP REST API, so you can dispatch notifications and manage resources from any language (cURL, Python, Go, and so on). The same project API key goes in the `Authorization` header (an `x-api-key` header works too):
209
197
 
210
198
  ```bash
211
199
  curl -X POST http://localhost:3000/v1/notify \
212
200
  -H "Content-Type: application/json" \
201
+ -H "Authorization: Bearer $NOTIFKIT_API_KEY" \
213
202
  -d '{
214
203
  "user": "usr_123",
215
204
  "template": "order-shipped",
@@ -218,24 +207,17 @@ curl -X POST http://localhost:3000/v1/notify \
218
207
  }'
219
208
  ```
220
209
 
221
- ### Development vs. Production
222
-
223
- - **Local Development**: Docker is the only prerequisite. In development, notifkit starts throwaway PostgreSQL and Redis containers automatically.
224
- - **Production**: Node 22+, PostgreSQL, Redis. Run migrations by pointing `drizzle-kit` at `node_modules/notifkit/drizzle`.
210
+ ### Development vs. production
225
211
 
226
- ---
212
+ Locally, Docker is the only prerequisite: in development notifkit starts throwaway PostgreSQL and Redis containers for you. In production you need Node 22+, PostgreSQL, and Redis, and you run migrations by pointing `drizzle-kit` at `node_modules/notifkit/drizzle`.
227
213
 
228
- ## Battle-tested for production
214
+ ## Running in production
229
215
 
230
- > **Battle-tested in production:** notifkit powers production notification pipelines handling **thousands of emails, push notifications, and OTPs every day.**
231
- >
232
- > It is the infrastructure we built because we needed it ourselves — rather than spending months reinventing distributed notification plumbing or paying SaaS tolls per alert.
216
+ notifkit runs in production at my own company, delivering 100K+ notifications a day across email, push, and OTPs. I built it because I needed it and didn't want to spend months rebuilding distributed notification plumbing or pay a SaaS per alert. It runs on your servers, with your provider accounts and your data.
233
217
 
234
- **Your servers. Your providers. Your data. Zero notification SaaS markups.**
218
+ ### Reliability and failure testing
235
219
 
236
- ### Reliability & Chaos Engineering
237
-
238
- Because notification delivery is mission-critical, every pipeline component is tested against extreme failure conditions:
220
+ Every component of the pipeline is tested against failure:
239
221
 
240
222
  ```mermaid
241
223
  flowchart LR
@@ -251,45 +233,97 @@ flowchart LR
251
233
  class O1,O2,O3 result
252
234
  ```
253
235
 
254
- - **Chaos Monkey Testing (`tests/chaos/crash.test.ts`)**: Background worker processes are randomly terminated with `SIGKILL` during active, high-throughput message streaming. Consumer group Pending Entries List (PEL) re-claims guarantee **zero lost messages** and seamless failover.
255
- - **Infrastructure Recovery Testing (`tests/chaos/recovery.test.ts`)**: PostgreSQL and Redis connections are forcefully severed and restored under live traffic. Verifies automatic client reconnection, worker backpressure, and durable state resumption.
256
- - **High-Throughput Load Testing (`tests/chaos/load.test.ts`)**: Stressed with bursts of **10,000+ notifications** across parallel worker pools, verifying queue drain velocity, sliding-window rate limiters, and flat memory profiles without leaks.
257
- - **Race Conditions & Concurrency (`tests/race-conditions.test.ts`, `tests/idempotency.test.ts`)**: Hardened against concurrent duplicate dispatches, overlapping quiet-hour boundary evaluations, atomic user updates, and 24-hour idempotency key deduplication.
258
- - **100% Real Ephemeral Containers**: Unit, integration, and chaos test suites execute against real PostgreSQL and Redis containers via [Testcontainers](https://testcontainers.com), eliminating mocks for core storage and streaming primitives.
259
-
260
- ---
236
+ - Crash testing (`tests/chaos/crash.test.ts`): background worker processes are killed with `SIGKILL` during high-throughput message streaming. Consumer group Pending Entries List (PEL) re-claims mean no messages are lost and another worker takes over.
237
+ - Infrastructure recovery (`tests/chaos/recovery.test.ts`): PostgreSQL and Redis connections are severed and restored under live traffic, verifying client reconnection, worker backpressure, and durable state resumption.
238
+ - Load testing (`tests/chaos/load.test.ts`): bursts of 10,000+ notifications across parallel worker pools, checking queue drain speed, sliding-window rate limiters, and memory use over time.
239
+ - Race conditions and concurrency (`tests/race-conditions.test.ts`, `tests/idempotency.test.ts`): concurrent duplicate dispatches, overlapping quiet-hour boundary evaluations, atomic user updates, and 24-hour idempotency key deduplication.
240
+ - Real containers, no mocks: unit, integration, and chaos suites all run against real PostgreSQL and Redis containers via [Testcontainers](https://testcontainers.com).
261
241
 
262
242
  ## What you get
263
243
 
264
- | The problem you don't want to build | How notifkit solves it |
265
- | :------------------------------------------------------- | :------------------------------------------------------------------- |
266
- | **“Should this user receive it?”** | User preferences, topic opt-outs, and consent gates |
267
- | **“Is this a bad time to send?”** | Timezone-aware quiet hours that defer non-urgent sends |
268
- | **“What if push fails?”** | Automatic ordered multi-channel fallback (`push` `email` `sms`) |
269
- | **“What if my worker crashes?”** | Redis Streams consumer groups, retries, and durable idempotency |
270
- | **“What if an event fires twice?”** | 24-hour deduplication via idempotency keys |
271
- | **“Can I send this later?”** | Priority scheduling with `sendAt` and cancellation before dispatch |
272
- | **“Can I send this 3 days after signup?”** | Stateful multi-step workflows with `wait` and `waitForEvent` |
273
- | **“How do I know what happened?”** | Queryable delivery logs, Prometheus metrics, and campaign reporting |
274
- | **“What happens when a provider goes down?”** | Circuit breakers, exponential backoff, and DLQ replay |
275
- | **“What about bounces and spam complaints?”** | RFC 8058 one-click unsubscribe and automatic hard-bounce suppression |
276
- | **“What if I don't want another SaaS holding my data?”** | 100% self-hosted on your PostgreSQL and Redis |
244
+ | The problem you don't want to build | How notifkit solves it |
245
+ | :--------------------------------------------------- | :------------------------------------------------------------------- |
246
+ | "Should this user receive it?" | User preferences, topic opt-outs, and consent gates |
247
+ | "Is this a bad time to send?" | Timezone-aware quiet hours that defer non-urgent sends |
248
+ | "What if push fails?" | Ordered multi-channel fallback (`push`, then `email`, then `sms`) |
249
+ | "What if my worker crashes?" | Redis Streams consumer groups, retries, and durable idempotency |
250
+ | "What if an event fires twice?" | 24-hour deduplication via idempotency keys |
251
+ | "Can I send this later?" | Priority scheduling with `sendAt` and cancellation before dispatch |
252
+ | "Can I send this 3 days after signup?" | Stateful multi-step workflows with `wait` and `waitForEvent` |
253
+ | "How do I know what happened?" | Queryable delivery logs, Prometheus metrics, and campaign reporting |
254
+ | "What happens when a provider goes down?" | Circuit breakers, exponential backoff, and DLQ replay |
255
+ | "What about bounces and spam complaints?" | RFC 8058 one-click unsubscribe and automatic hard-bounce suppression |
256
+ | "What if I don't want another SaaS holding my data?" | Fully self-hosted on your PostgreSQL and Redis |
257
+
258
+ You decide what to say. notifkit gets it there.
259
+
260
+ ## Scope
261
+
262
+ notifkit is the durable notification layer that runs inside your own stack. It is not a marketing automation suite, and it does not replace Customer.io, OneSignal, or SendGrid. You bring your own provider accounts and pay them directly.
263
+
264
+ First-party providers cover Resend, Firebase Cloud Messaging, Slack, Twilio, Telegram, Discord, and WhatsApp. Anything else is a `Transport` class with a `send()` method.
265
+
266
+ ### How this compares to Novu
267
+
268
+ Novu is the established open-source project in this space, and if you want a notification platform with a dashboard, a visual workflow editor, and a drop-in in-app inbox component, use Novu. It is more mature, has a much larger community, and solves a broader problem.
269
+
270
+ notifkit is a narrower, more embeddable take on the same layer:
271
+
272
+ - **A library first, a platform second.** notifkit is an npm package you can run inside your existing Node process. Novu self-hosts as a set of services (API, worker, WebSocket server, dashboard SPA) that you deploy and operate alongside your app.
273
+ - **Postgres, not MongoDB.** notifkit stores state in PostgreSQL with Drizzle migrations and queues in Redis Streams. If Postgres is already your database, there is no new datastore to run.
274
+ - **Workflows as code.** Multi-step sequences are typed TypeScript, versioned in your repo, rather than built in a visual editor.
275
+ - **MIT, all of it.** There is no open-core split. Novu is MIT at the core with enterprise features under a commercial license; notifkit has no feature held back from the self-hosted build.
276
+ - **MCP as a first-class interface.** Agents operate the same infrastructure your app uses, including triage and delivery-log inspection.
277
+
278
+ What notifkit does not have: an in-app notification center or inbox component, a web dashboard for non-engineers, digest aggregation, or Novu's provider catalog. If you need those, Novu is the better fit.
279
+
280
+ ## Agent-operable
281
+
282
+ https://github.com/user-attachments/assets/4dff98bb-37d3-44b4-bf46-9607c1cd89b5
283
+
284
+ An AI agent can operate notifkit directly. Connect the notifkit MCP server ([`@notifkit/mcp`](./packages/mcp)) to Claude Code, Cursor, Claude Desktop, Gemini, or any MCP-compatible agent:
285
+
286
+ ```bash
287
+ npx -y @notifkit/mcp
288
+ ```
289
+
290
+ ### Ask your agent
291
+
292
+ ```text
293
+ You: Why didn't usr_9182 receive their password reset?
294
+
295
+ Agent: The notification was suppressed because usr_9182's email
296
+ address has a hard-bounce suppression from yesterday.
297
+ ```
298
+
299
+ Your application and your AI agents use the same notification infrastructure. Through MCP an agent can:
277
300
 
278
- > **The idea is simple:** You decide what to say. **notifkit handles getting it there reliably.**
301
+ - Send one-off notifications or campaigns to users, lists, and segments (`send_notification`, `send_campaign`)
302
+ - Diagnose delivery issues by inspecting message histories, provider responses, and quiet hours (`get_delivery_logs`, `get_notification`)
303
+ - Schedule future sends and cancel pending notifications (`list_scheduled`, `cancel_notification`)
304
+ - Check delivery, open, click, bounce, and complaint metrics (`list_campaigns`, `get_campaign_stats`)
305
+ - List, preview, and update templates with sample data (`list_templates`, `preview_template`, `upsert_template`)
306
+ - Look up users, contacts, preferences, and segment membership (`list_users`, `get_user_preferences`, `update_user_preferences`)
307
+ - Trigger workflows and inspect workflow runs (`create_workflow`, `trigger_workflow`, `get_workflow_run`)
308
+ - Manage bounce suppressions, check system queues, and replay dead-letter messages (`list_suppressions`, `get_dead_letters`, `replay_dead_letter`)
279
309
 
280
- ---
310
+ ### The same task, with and without an agent
281
311
 
282
- ## What notifkit is and what it isn't
312
+ | Without an agent | With the notifkit MCP server |
313
+ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
314
+ | Query the database for contact info, open Twilio or Resend or write a throwaway script, format the payload, check the user's timezone by hand, send it, and hope it delivered. | **You:** _"Send an urgent update to alex@acme.com that his package was lost in transit and support is rushing a replacement. Text him if push doesn't deliver."_<br><br>**Agent:** Looks up `alex@acme.com`, renders the template, dispatches push with SMS fallback, bypasses quiet hours because the send is urgent, tracks delivery status, and confirms it reached his phone. |
283
315
 
284
- **What it is:** the durable notification infrastructure layer running directly inside your own stack.
316
+ [MCP documentation](https://notifkit.dev/docs/mcp.html)
285
317
 
286
- **What it isn't:** a marketing automation suite.
318
+ ## AI-assisted migration
287
319
 
288
- notifkit is not Customer.io, OneSignal, or SendGrid. You bring your own provider accounts — your keys, your billing, your deliverability.
320
+ Already have notification code scattered across your application? Point your coding agent at:
289
321
 
290
- First-party providers ship for Resend and Firebase Cloud Messaging. Anything else is a simple `Transport` class with a `send()` method.
322
+ ```text
323
+ https://notifkit.dev/llms-full.txt
324
+ ```
291
325
 
292
- ---
326
+ It can read notifkit's API from there, find ad-hoc notification code in your repository, and refactor it into notifkit calls.
293
327
 
294
328
  ## Feature matrix
295
329
 
@@ -297,7 +331,7 @@ First-party providers ship for Resend and Firebase Cloud Messaging. Anything els
297
331
  | :------------------ | :------------------------------------------------------------------------------------- |
298
332
  | **Channels** | `email`, `sms`, `push`, `webhook`, `telegram`, `discord`, `whatsapp`, `slack` |
299
333
  | **Targeting** | A user, a list of users, a segment, or a topic |
300
- | **Priorities** | `low`, `normal`, `high`, `critical` separate stream lanes |
334
+ | **Priorities** | `low`, `normal`, `high`, `critical`, on separate stream lanes |
301
335
  | **Scheduling** | Future sends with `sendAt`, quiet-hours deferral, cancellation |
302
336
  | **Preferences** | Per-user channel and topic opt-outs, quiet hours, contact-level overrides |
303
337
  | **Workflows** | Multi-step sequences with `wait`, `waitForEvent`, and `notify` steps |
@@ -310,23 +344,19 @@ First-party providers ship for Resend and Firebase Cloud Messaging. Anything els
310
344
  | **Agent operation** | MCP server for sending, triage, campaigns, templates, workflows, and system operations |
311
345
  | **Observability** | Prometheus `/metrics`, `/health`, `/live`, `/ready`, and queryable delivery logs |
312
346
 
313
- ---
314
-
315
347
  ## Providers
316
348
 
317
- Bring your own provider accounts.
318
-
319
- First-party packages:
349
+ Bring your own provider accounts. First-party packages:
320
350
 
321
- - [`@notifkit/provider-resend`](./packages/provider-resend) transactional email via Resend
322
- - [`@notifkit/provider-fcm`](./packages/provider-fcm) push notifications via Firebase Cloud Messaging
323
- - [`@notifkit/provider-slack`](./packages/provider-slack) Slack messages via Incoming Webhooks or the Web API
324
- - [`@notifkit/provider-twilio`](./packages/provider-twilio) SMS via Twilio, with signature-verified delivery status callbacks
325
- - [`@notifkit/provider-telegram`](./packages/provider-telegram) messages via a Telegram bot
326
- - [`@notifkit/provider-discord`](./packages/provider-discord) messages via a Discord webhook
327
- - [`@notifkit/provider-whatsapp`](./packages/provider-whatsapp) messages via Meta's WhatsApp Cloud API
351
+ - [`@notifkit/provider-resend`](./packages/provider-resend): transactional email via Resend
352
+ - [`@notifkit/provider-fcm`](./packages/provider-fcm): push notifications via Firebase Cloud Messaging
353
+ - [`@notifkit/provider-slack`](./packages/provider-slack): Slack messages via Incoming Webhooks or the Web API
354
+ - [`@notifkit/provider-twilio`](./packages/provider-twilio): SMS via Twilio, with signature-verified delivery status callbacks
355
+ - [`@notifkit/provider-telegram`](./packages/provider-telegram): messages via a Telegram bot
356
+ - [`@notifkit/provider-discord`](./packages/provider-discord): messages via a Discord webhook
357
+ - [`@notifkit/provider-whatsapp`](./packages/provider-whatsapp): messages via Meta's WhatsApp Cloud API
328
358
 
329
- For anything else, implement a simple `Transport`:
359
+ For anything else, implement a `Transport`:
330
360
 
331
361
  ```ts
332
362
  class MyTransport implements Transport {
@@ -337,9 +367,7 @@ class MyTransport implements Transport {
337
367
  }
338
368
  ```
339
369
 
340
- **Your keys. Your billing. Your deliverability.**
341
-
342
- ---
370
+ The keys, the billing, and the deliverability stay yours.
343
371
 
344
372
  ## Documentation
345
373
 
@@ -361,27 +389,13 @@ Everything lives at [**notifkit.dev/docs**](https://notifkit.dev/docs/).
361
389
  | [Reference](https://notifkit.dev/docs/reference.html) | API, payloads, and SDK methods |
362
390
  | [MCP server](https://notifkit.dev/docs/mcp.html) | Operate notifkit from an AI agent |
363
391
 
364
- ---
365
-
366
392
  ## Why build this?
367
393
 
368
- Because notification infrastructure looks simple until you're responsible for it.
369
-
370
- You can spend months building queues, retries, provider adapters, preference systems, quiet-hour logic, workflows, suppression handling, and operational tooling.
371
-
372
- Or you can use the infrastructure we built for ourselves.
373
-
374
- **notifkit exists so your team can spend its time building the product — not another notification platform.**
375
-
376
- ---
377
-
378
- ## Star the repo ⭐
379
-
380
- If notifkit saves you a month or two you were about to spend building this yourself, **[give it a star on GitHub](https://github.com/devkitshq/notifkit)** — it's the cheapest way to help other people find it.
394
+ Notification infrastructure looks simple until you're responsible for it. Queues, retries, provider adapters, preference systems, quiet-hour logic, workflows, suppression handling, and operational tooling take months to build well. notifkit is what I built instead, and it's what I run.
381
395
 
382
396
  ## Contributing
383
397
 
384
- Issues and pull requests are welcome.
398
+ Issues and pull requests are welcome. Stars help other people find the project.
385
399
 
386
400
  ```bash
387
401
  npm install
@@ -393,10 +407,7 @@ The test suite starts its own PostgreSQL and Redis containers, so Docker is the
393
407
 
394
408
  ## Contact
395
409
 
396
- Questions, bugs, or ideas — mail me. I run this on my own company, which delivers a lot of notifications daily (100K+/day).
397
-
398
- - **Email:** [contact.devkitshq@gmail.com](mailto:contact.devkitshq@gmail.com)
399
- - **Book a 30-min call:** [calendly.com/contact-devkitshq/30min](https://calendly.com/contact-devkitshq/30min)
410
+ Questions, bugs, or ideas: [contact.devkitshq@gmail.com](mailto:contact.devkitshq@gmail.com), or open an issue.
400
411
 
401
412
  ## License
402
413
 
package/dist/index.d.mts CHANGED
@@ -461,8 +461,8 @@ declare const NotificationEnrichedPayloadSchema: z.ZodObject<{
461
461
  channel: "push" | "email" | "sms" | "webhook" | "telegram" | "discord" | "whatsapp" | "slack" | "in-app";
462
462
  priority: "low" | "normal" | "high" | "critical";
463
463
  projectId: string;
464
- recipientId: string;
465
464
  rawEventId: string;
465
+ recipientId: string;
466
466
  templateVariables: Record<string, unknown>;
467
467
  recipient: {
468
468
  id: string;
@@ -494,8 +494,8 @@ declare const NotificationEnrichedPayloadSchema: z.ZodObject<{
494
494
  channel: "push" | "email" | "sms" | "webhook" | "telegram" | "discord" | "whatsapp" | "slack" | "in-app";
495
495
  priority: "low" | "normal" | "high" | "critical";
496
496
  projectId: string;
497
- recipientId: string;
498
497
  rawEventId: string;
498
+ recipientId: string;
499
499
  templateVariables: Record<string, unknown>;
500
500
  recipient: {
501
501
  id: string;
@@ -736,8 +736,8 @@ declare const NotificationDispatchedPayloadSchema: z.ZodObject<{
736
736
  projectId: string;
737
737
  taskId: string;
738
738
  recipientId: string;
739
- enrichedEventId: string;
740
739
  templateVariables: Record<string, unknown>;
740
+ enrichedEventId: string;
741
741
  renderedContent: {
742
742
  content: Record<string, unknown>;
743
743
  attachments?: {
@@ -1042,7 +1042,6 @@ declare const NotificationAiPendingPayloadSchema: z.ZodObject<{
1042
1042
  aiPrompts: Record<string, string>;
1043
1043
  projectId: string;
1044
1044
  recipientId: string;
1045
- enrichedEventId: string;
1046
1045
  templateVariables: Record<string, unknown>;
1047
1046
  recipient: {
1048
1047
  id: string;
@@ -1065,6 +1064,7 @@ declare const NotificationAiPendingPayloadSchema: z.ZodObject<{
1065
1064
  pushToken?: string | undefined;
1066
1065
  pushTokens?: string[] | undefined;
1067
1066
  };
1067
+ enrichedEventId: string;
1068
1068
  templateId?: string | undefined;
1069
1069
  scheduledAt?: string | undefined;
1070
1070
  fallbackChain?: ("push" | "email" | "sms" | "webhook" | "telegram" | "discord" | "whatsapp" | "slack" | "in-app")[] | undefined;
@@ -1074,7 +1074,6 @@ declare const NotificationAiPendingPayloadSchema: z.ZodObject<{
1074
1074
  aiPrompts: Record<string, string>;
1075
1075
  projectId: string;
1076
1076
  recipientId: string;
1077
- enrichedEventId: string;
1078
1077
  templateVariables: Record<string, unknown>;
1079
1078
  recipient: {
1080
1079
  id: string;
@@ -1097,6 +1096,7 @@ declare const NotificationAiPendingPayloadSchema: z.ZodObject<{
1097
1096
  pushTokens?: string[] | undefined;
1098
1097
  locale?: string | undefined;
1099
1098
  };
1099
+ enrichedEventId: string;
1100
1100
  templateId?: string | undefined;
1101
1101
  scheduledAt?: string | undefined;
1102
1102
  fallbackChain?: ("push" | "email" | "sms" | "webhook" | "telegram" | "discord" | "whatsapp" | "slack" | "in-app")[] | undefined;