notifkit 0.1.3 → 0.1.5

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.
Files changed (96) hide show
  1. package/README.md +179 -152
  2. package/dist/index.d.mts +193 -129
  3. package/dist/index.d.mts.map +1 -1
  4. package/dist/index.mjs +1 -1
  5. package/dist/index.mjs.map +1 -1
  6. package/dist/{main-DtHWhueo.mjs → main-40zwq6b0.mjs} +28 -3
  7. package/dist/{main-DtHWhueo.mjs.map → main-40zwq6b0.mjs.map} +1 -1
  8. package/dist/{main-DyfbnJc3.mjs → main-BFre2-HQ.mjs} +2 -2
  9. package/dist/{main-DyfbnJc3.mjs.map → main-BFre2-HQ.mjs.map} +1 -1
  10. package/dist/{main-CAH0_Q6d.mjs → main-BNJtzY61.mjs} +3 -3
  11. package/dist/main-BNJtzY61.mjs.map +1 -0
  12. package/dist/{main-B561M1d3.mjs → main-BOPMYqsW.mjs} +2 -2
  13. package/dist/{main-B561M1d3.mjs.map → main-BOPMYqsW.mjs.map} +1 -1
  14. package/dist/{main-CCfc45ev.mjs → main-CiigNpsP.mjs} +7 -4
  15. package/dist/main-CiigNpsP.mjs.map +1 -0
  16. package/dist/{main-Ce9dcrsg.mjs → main-DeNFQ-UL.mjs} +6 -3
  17. package/dist/{main-Ce9dcrsg.mjs.map → main-DeNFQ-UL.mjs.map} +1 -1
  18. package/dist/{main-B-jwm8ED.mjs → main-DmCPcxOc.mjs} +2 -2
  19. package/dist/{main-B-jwm8ED.mjs.map → main-DmCPcxOc.mjs.map} +1 -1
  20. package/dist/{main-C45e7grq.mjs → main-DvgJSm11.mjs} +2 -2
  21. package/dist/{main-C45e7grq.mjs.map → main-DvgJSm11.mjs.map} +1 -1
  22. package/dist/{src-C-PfEDMY.mjs → src-vG79L-8m.mjs} +57 -26
  23. package/dist/src-vG79L-8m.mjs.map +1 -0
  24. package/drizzle/0002_wide_colleen_wing.sql +2 -0
  25. package/drizzle/0003_skinny_daimon_hellstrom.sql +1 -0
  26. package/drizzle/0004_pretty_bruce_banner.sql +1 -0
  27. package/drizzle/meta/0002_snapshot.json +1460 -0
  28. package/drizzle/meta/0003_snapshot.json +1460 -0
  29. package/drizzle/meta/0004_snapshot.json +1470 -0
  30. package/drizzle/meta/_journal.json +21 -0
  31. package/package.json +7 -1
  32. package/scripts/create-project.mjs +61 -0
  33. package/src/client.ts +412 -0
  34. package/src/config/index.ts +107 -0
  35. package/src/contracts/common.ts +28 -0
  36. package/src/contracts/envelope.ts +31 -0
  37. package/src/contracts/events/notification-ai-pending.ts +18 -0
  38. package/src/contracts/events/notification-canceled.ts +7 -0
  39. package/src/contracts/events/notification-created.ts +14 -0
  40. package/src/contracts/events/notification-delivered.ts +17 -0
  41. package/src/contracts/events/notification-dispatched.ts +45 -0
  42. package/src/contracts/events/notification-enriched.ts +46 -0
  43. package/src/contracts/events/notification-failed.ts +19 -0
  44. package/src/contracts/events/notification-requested.ts +36 -0
  45. package/src/contracts/events/notification-scheduled.ts +9 -0
  46. package/src/contracts/events/notification-skipped.ts +9 -0
  47. package/src/contracts/helpers.ts +21 -0
  48. package/src/contracts/index.ts +46 -0
  49. package/src/contracts/metadata.ts +10 -0
  50. package/src/contracts/registry.ts +88 -0
  51. package/src/contracts/sdk.ts +242 -0
  52. package/src/contracts/streams.ts +62 -0
  53. package/src/db/index.ts +69 -0
  54. package/src/db/schema.ts +412 -0
  55. package/src/idempotency/index.ts +50 -0
  56. package/src/index.ts +19 -0
  57. package/src/logger/index.ts +60 -0
  58. package/src/metrics/index.ts +53 -0
  59. package/src/queue/index.ts +501 -0
  60. package/src/rate-limiter/index.ts +210 -0
  61. package/src/redis/index.ts +89 -0
  62. package/src/repositories/index.ts +1246 -0
  63. package/src/server.ts +277 -0
  64. package/src/services/ai/main.ts +404 -0
  65. package/src/services/api/handlers.ts +1734 -0
  66. package/src/services/api/http.ts +64 -0
  67. package/src/services/api/main.ts +693 -0
  68. package/src/services/api/router.ts +82 -0
  69. package/src/services/delivery/main.ts +842 -0
  70. package/src/services/delivery/throttle.ts +71 -0
  71. package/src/services/engine/main.ts +827 -0
  72. package/src/services/enricher/main.ts +594 -0
  73. package/src/services/events/main.ts +365 -0
  74. package/src/services/scheduler/main.ts +319 -0
  75. package/src/services/workflow/main.ts +627 -0
  76. package/src/shared/batch-processor.ts +67 -0
  77. package/src/shared/cache.ts +47 -0
  78. package/src/shared/circuit-breaker.ts +74 -0
  79. package/src/shared/dataloader.ts +41 -0
  80. package/src/shared/events.ts +3 -0
  81. package/src/shared/index.ts +39 -0
  82. package/src/shared/semaphore.ts +33 -0
  83. package/src/shared/utils.ts +64 -0
  84. package/src/templates/cache.ts +32 -0
  85. package/src/templates/index.ts +69 -0
  86. package/src/templates/render.ts +128 -0
  87. package/src/transport/index.ts +96 -0
  88. package/src/unsubscribe/index.ts +127 -0
  89. package/src/workers/health.ts +31 -0
  90. package/src/workers/index.ts +266 -0
  91. package/src/workflows/index.ts +2 -0
  92. package/src/workflows/registry.ts +21 -0
  93. package/src/workflows/sdk.ts +106 -0
  94. package/dist/main-CAH0_Q6d.mjs.map +0 -1
  95. package/dist/main-CCfc45ev.mjs.map +0 -1
  96. package/dist/src-C-PfEDMY.mjs.map +0 -1
package/README.md CHANGED
@@ -4,17 +4,15 @@
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
- [![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/badge/coverage-85%25-brightgreen.svg?style=flat-square)](https://github.com/devkitshq/notifkit) [![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)
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
 
11
11
  [Documentation](https://notifkit.dev/docs/) · [Quickstart](https://notifkit.dev/docs/quickstart.html) · [Examples](https://notifkit.dev/docs/examples.html) · [notifkit.dev](https://notifkit.dev)
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,107 +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
- ## Battle-tested for production
103
-
104
- > **Battle-tested in production:** notifkit powers production notification pipelines handling **thousands of emails, push notifications, and OTPs every day.**
105
- >
106
- > 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.
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.
107
87
 
108
- **Your servers. Your providers. Your data. Zero notification SaaS markups.**
109
-
110
- ### Reliability & Chaos Engineering
111
-
112
- Because notification delivery is mission-critical, every pipeline component is tested against extreme failure conditions:
88
+ ## Quickstart
113
89
 
114
- ```mermaid
115
- flowchart LR
116
- S1["Redis Streams"] -->|"Kill Worker (SIGKILL)"| M1["Auto-Claim and Replay"] --> O1["Zero Lost Messages"]
117
- S2["Connection Loss"] -->|"Drop DB / Redis"| M2["Auto-Reconnect / Retry"] --> O2["In-Flight State Intact"]
118
- S3["10k+ Messages"] -->|"Burst"| M3["Concurrency and Limits"] --> O3["Flat Memory, No Leaks"]
90
+ ### 1. Install
119
91
 
120
- classDef fault stroke:#ef4444,stroke-width:2px
121
- classDef guard stroke:#6366f1,stroke-width:2px
122
- classDef result stroke:#22c55e,stroke-width:2px
123
- class S1,S2,S3 fault
124
- class M1,M2,M3 guard
125
- class O1,O2,O3 result
92
+ ```bash
93
+ npm install notifkit @notifkit/provider-resend
94
+ npm install -D tsx @testcontainers/postgresql @testcontainers/redis
126
95
  ```
127
96
 
128
- - **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.
129
- - **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.
130
- - **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.
131
- - **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.
132
- - **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.
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.
133
98
 
134
- ---
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.
135
101
 
136
- ## What you get
102
+ ### 2. Run the engine
137
103
 
138
- | The problem you don't want to build | How notifkit solves it |
139
- | :------------------------------------------------------- | :------------------------------------------------------------------- |
140
- | **“Should this user receive it?”** | User preferences, topic opt-outs, and consent gates |
141
- | **“Is this a bad time to send?”** | Timezone-aware quiet hours that defer non-urgent sends |
142
- | **“What if push fails?”** | Automatic ordered multi-channel fallback (`push` → `email` → `sms`) |
143
- | **“What if my worker crashes?”** | Redis Streams consumer groups, retries, and durable idempotency |
144
- | **“What if an event fires twice?”** | 24-hour deduplication via idempotency keys |
145
- | **“Can I send this later?”** | Priority scheduling with `sendAt` and cancellation before dispatch |
146
- | **“Can I send this 3 days after signup?”** | Stateful multi-step workflows with `wait` and `waitForEvent` |
147
- | **“How do I know what happened?”** | Queryable delivery logs, Prometheus metrics, and campaign reporting |
148
- | **“What happens when a provider goes down?”** | Circuit breakers, exponential backoff, and DLQ replay |
149
- | **“What about bounces and spam complaints?”** | RFC 8058 one-click unsubscribe and automatic hard-bounce suppression |
150
- | **“What if I don't want another SaaS holding my data?”** | 100% self-hosted on your PostgreSQL and Redis |
104
+ `server.ts` starts the API and the worker pipelines. In development it auto-starts those containers, so Docker is the only prerequisite.
151
105
 
152
- > **The idea is simple:** You decide what to say. **notifkit handles getting it there reliably.**
106
+ ```ts
107
+ // server.ts
108
+ import { NotifkitServer } from "notifkit";
109
+ import { ResendTransport } from "@notifkit/provider-resend";
153
110
 
154
- ---
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
+ });
155
121
 
156
- ## What notifkit is — and what it isn't
122
+ await server.start();
123
+ console.log("notifkit listening on http://localhost:3000");
124
+ ```
157
125
 
158
- **What it is:** the durable notification infrastructure layer running directly inside your own stack.
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:
159
127
 
160
- **What it isn't:** a marketing automation suite.
128
+ ```bash
129
+ ADMIN_API_KEY=supersecretkey RESEND_API_KEY=re_xxx npx tsx server.ts
130
+ ```
161
131
 
162
- notifkit is not Customer.io, OneSignal, or SendGrid. You bring your own provider accounts — your keys, your billing, your deliverability.
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.
163
134
 
164
- First-party providers ship for Resend and Firebase Cloud Messaging. Anything else is a simple `Transport` class with a `send()` method.
135
+ ### 3. Create a project and its API key
165
136
 
166
- ---
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:
167
138
 
168
- ## Quickstart
139
+ ```bash
140
+ ADMIN_API_KEY=supersecretkey npx notifkit-create-project "my-app"
141
+ ```
169
142
 
170
- ### 1. Install
143
+ ```
144
+ Project "my-app" created. Save the API key now — it is not recoverable.
171
145
 
172
- ```bash
173
- npm install notifkit @notifkit/provider-resend
146
+ NOTIFKIT_PROJECT_ID=1ce67fa1-b4a9-4985-8046-ef6018912b2a
147
+ NOTIFKIT_API_KEY=nk_live_f57c57b76d795cef89e2dbf6b6f352a36…
174
148
  ```
175
149
 
176
- ### 2. Run the engine and dispatch your first notification
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).
151
+
152
+ ### 4. Dispatch your first notification
153
+
154
+ `client.ts` is your application code. It talks to the server over HTTP: register a template, register a user, and send.
177
155
 
178
156
  ```ts
179
- import { NotifkitServer, NotifkitClient } from "notifkit";
180
- import { ResendTransport } from "@notifkit/provider-resend";
157
+ // client.ts
158
+ import { NotifkitClient } from "notifkit";
181
159
 
182
- // 1. Start the server (runs API + workers; auto-starts Postgres & Redis in dev)
183
- const server = new NotifkitServer({
184
- services: ["all"],
185
- port: 3000,
186
- 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!,
187
163
  });
188
- await server.start();
189
-
190
- // 2. Instantiate client and register a template
191
- const notifkit = new NotifkitClient({ baseUrl: "http://localhost:3000" });
192
164
 
165
+ // 1. Register a template
193
166
  await notifkit.syncTemplates({
194
167
  templates: [
195
168
  {
@@ -200,9 +173,10 @@ await notifkit.syncTemplates({
200
173
  ],
201
174
  });
202
175
 
203
- // 3. Register user and dispatch
176
+ // 2. Register a user
204
177
  await notifkit.addUser({ id: "usr_123", email: "alex@acme.com" });
205
178
 
179
+ // 3. Dispatch
206
180
  await notifkit.notify({
207
181
  user: "usr_123",
208
182
  template: "order-shipped",
@@ -211,13 +185,20 @@ await notifkit.notify({
211
185
  });
212
186
  ```
213
187
 
214
- ### 3. Or call directly via REST API
188
+ With the server still running in the first terminal, run the client in a second one:
215
189
 
216
- 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):
217
197
 
218
198
  ```bash
219
199
  curl -X POST http://localhost:3000/v1/notify \
220
200
  -H "Content-Type: application/json" \
201
+ -H "Authorization: Bearer $NOTIFKIT_API_KEY" \
221
202
  -d '{
222
203
  "user": "usr_123",
223
204
  "template": "order-shipped",
@@ -226,18 +207,81 @@ curl -X POST http://localhost:3000/v1/notify \
226
207
  }'
227
208
  ```
228
209
 
229
- ### Development vs. Production
210
+ ### Development vs. production
211
+
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`.
213
+
214
+ ## Running in production
215
+
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.
230
217
 
231
- - **Local Development**: Docker is the only prerequisite. In development, notifkit starts throwaway PostgreSQL and Redis containers automatically.
232
- - **Production**: Node 22+, PostgreSQL, Redis. Run migrations by pointing `drizzle-kit` at `node_modules/notifkit/drizzle`.
218
+ ### Reliability and failure testing
219
+
220
+ Every component of the pipeline is tested against failure:
221
+
222
+ ```mermaid
223
+ flowchart LR
224
+ S1["Redis Streams"] -->|"Kill Worker (SIGKILL)"| M1["Auto-Claim and Replay"] --> O1["Zero Lost Messages"]
225
+ S2["Connection Loss"] -->|"Drop DB / Redis"| M2["Auto-Reconnect / Retry"] --> O2["In-Flight State Intact"]
226
+ S3["10k+ Messages"] -->|"Burst"| M3["Concurrency and Limits"] --> O3["Flat Memory, No Leaks"]
227
+
228
+ classDef fault stroke:#ef4444,stroke-width:2px
229
+ classDef guard stroke:#6366f1,stroke-width:2px
230
+ classDef result stroke:#22c55e,stroke-width:2px
231
+ class S1,S2,S3 fault
232
+ class M1,M2,M3 guard
233
+ class O1,O2,O3 result
234
+ ```
235
+
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).
241
+
242
+ ## What you get
233
243
 
234
- ---
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.
235
279
 
236
280
  ## Agent-operable
237
281
 
238
- **notifkit isn't just an API your application can call — your AI agent can operate it directly.**
282
+ https://github.com/user-attachments/assets/4dff98bb-37d3-44b4-bf46-9607c1cd89b5
239
283
 
240
- Connect the notifkit MCP server ([`@notifkit/mcp`](./packages/mcp)) to Claude Code, Cursor, Claude Desktop, Gemini, or any MCP-compatible agent:
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:
241
285
 
242
286
  ```bash
243
287
  npx -y @notifkit/mcp
@@ -252,48 +296,42 @@ Agent: The notification was suppressed because usr_9182's email
252
296
  address has a hard-bounce suppression from yesterday.
253
297
  ```
254
298
 
255
- Your application and your AI agents use the **same notification infrastructure**:
299
+ Your application and your AI agents use the same notification infrastructure. Through MCP an agent can:
256
300
 
257
- - **Send & dispatch** — Send one-off notifications or campaigns to users, lists, and segments (`send_notification`, `send_campaign`)
258
- - **Investigate & triage** — Diagnose delivery issues by inspecting message histories, provider responses, and quiet hours (`get_delivery_logs`, `get_notification`)
259
- - **Schedule & cancel** — Schedule future sends and cancel pending notifications (`list_scheduled`, `cancel_notification`)
260
- - **Campaign analytics** — Check delivery, open, click, bounce, and complaint metrics (`list_campaigns`, `get_campaign_stats`)
261
- - **Template management** — List, preview, and update templates with sample data (`list_templates`, `preview_template`, `upsert_template`)
262
- - **Users & preferences** — Look up users, contacts, preferences, and segment membership (`list_users`, `get_user_preferences`, `update_user_preferences`)
263
- - **Workflow operations** — Trigger workflows and inspect workflow runs (`create_workflow`, `trigger_workflow`, `get_workflow_run`)
264
- - **Suppressions & health** — Manage bounce suppressions, check system queues, and replay dead-letter messages (`list_suppressions`, `get_dead_letters`, `replay_dead_letter`)
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`)
265
309
 
266
- ### From “write a script” to “just ask”
310
+ ### The same task, with and without an agent
267
311
 
268
- | Without an agent | With NotifKit MCP |
269
- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
270
- | 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 |
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. |
271
315
 
272
- [Set up MCP](https://notifkit.dev/docs/mcp.html) · [MCP documentation](https://notifkit.dev/docs/mcp.html)
273
-
274
- ---
316
+ [MCP documentation](https://notifkit.dev/docs/mcp.html)
275
317
 
276
318
  ## AI-assisted migration
277
319
 
278
- Already have notification code scattered across your application?
279
-
280
- Point your coding agent at:
320
+ Already have notification code scattered across your application? Point your coding agent at:
281
321
 
282
322
  ```text
283
323
  https://notifkit.dev/llms-full.txt
284
324
  ```
285
325
 
286
- It can understand notifkit's API and help identify ad-hoc notification code in your repository and refactor it into durable notifkit calls.
287
-
288
- ---
326
+ It can read notifkit's API from there, find ad-hoc notification code in your repository, and refactor it into notifkit calls.
289
327
 
290
328
  ## Feature matrix
291
329
 
292
330
  | | |
293
331
  | :------------------ | :------------------------------------------------------------------------------------- |
294
- | **Channels** | `email`, `sms`, `push`, `webhook` |
332
+ | **Channels** | `email`, `sms`, `push`, `webhook`, `telegram`, `discord`, `whatsapp`, `slack` |
295
333
  | **Targeting** | A user, a list of users, a segment, or a topic |
296
- | **Priorities** | `low`, `normal`, `high`, `critical` separate stream lanes |
334
+ | **Priorities** | `low`, `normal`, `high`, `critical`, on separate stream lanes |
297
335
  | **Scheduling** | Future sends with `sendAt`, quiet-hours deferral, cancellation |
298
336
  | **Preferences** | Per-user channel and topic opt-outs, quiet hours, contact-level overrides |
299
337
  | **Workflows** | Multi-step sequences with `wait`, `waitForEvent`, and `notify` steps |
@@ -306,31 +344,30 @@ It can understand notifkit's API and help identify ad-hoc notification code in y
306
344
  | **Agent operation** | MCP server for sending, triage, campaigns, templates, workflows, and system operations |
307
345
  | **Observability** | Prometheus `/metrics`, `/health`, `/live`, `/ready`, and queryable delivery logs |
308
346
 
309
- ---
310
-
311
347
  ## Providers
312
348
 
313
- Bring your own provider accounts.
314
-
315
- First-party packages:
349
+ Bring your own provider accounts. First-party packages:
316
350
 
317
- - [`@notifkit/provider-resend`](./packages/provider-resend) transactional email via Resend
318
- - [`@notifkit/provider-fcm`](./packages/provider-fcm) push notifications via Firebase Cloud Messaging
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
319
358
 
320
- For anything else, implement a simple `Transport`:
359
+ For anything else, implement a `Transport`:
321
360
 
322
361
  ```ts
323
362
  class MyTransport implements Transport {
324
363
  async send(message) {
325
- // Send through Twilio, SES, Postmark, APNs,
364
+ // Send through SES, Postmark, APNs,
326
365
  // SendGrid, a custom webhook, or anything else.
327
366
  }
328
367
  }
329
368
  ```
330
369
 
331
- **Your keys. Your billing. Your deliverability.**
332
-
333
- ---
370
+ The keys, the billing, and the deliverability stay yours.
334
371
 
335
372
  ## Documentation
336
373
 
@@ -352,27 +389,13 @@ Everything lives at [**notifkit.dev/docs**](https://notifkit.dev/docs/).
352
389
  | [Reference](https://notifkit.dev/docs/reference.html) | API, payloads, and SDK methods |
353
390
  | [MCP server](https://notifkit.dev/docs/mcp.html) | Operate notifkit from an AI agent |
354
391
 
355
- ---
356
-
357
392
  ## Why build this?
358
393
 
359
- Because notification infrastructure looks simple until you're responsible for it.
360
-
361
- You can spend months building queues, retries, provider adapters, preference systems, quiet-hour logic, workflows, suppression handling, and operational tooling.
362
-
363
- Or you can use the infrastructure we built for ourselves.
364
-
365
- **notifkit exists so your team can spend its time building the product — not another notification platform.**
366
-
367
- ---
368
-
369
- ## Star the repo ⭐
370
-
371
- 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.
372
395
 
373
396
  ## Contributing
374
397
 
375
- Issues and pull requests are welcome.
398
+ Issues and pull requests are welcome. Stars help other people find the project.
376
399
 
377
400
  ```bash
378
401
  npm install
@@ -382,6 +405,10 @@ npm test
382
405
 
383
406
  The test suite starts its own PostgreSQL and Redis containers, so Docker is the only thing you need running.
384
407
 
408
+ ## Contact
409
+
410
+ Questions, bugs, or ideas: [contact.devkitshq@gmail.com](mailto:contact.devkitshq@gmail.com), or open an issue.
411
+
385
412
  ## License
386
413
 
387
414
  MIT. Do what you like with it, including commercially. See [LICENSE](./LICENSE).