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.
- package/README.md +179 -152
- package/dist/index.d.mts +193 -129
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/{main-DtHWhueo.mjs → main-40zwq6b0.mjs} +28 -3
- package/dist/{main-DtHWhueo.mjs.map → main-40zwq6b0.mjs.map} +1 -1
- package/dist/{main-DyfbnJc3.mjs → main-BFre2-HQ.mjs} +2 -2
- package/dist/{main-DyfbnJc3.mjs.map → main-BFre2-HQ.mjs.map} +1 -1
- package/dist/{main-CAH0_Q6d.mjs → main-BNJtzY61.mjs} +3 -3
- package/dist/main-BNJtzY61.mjs.map +1 -0
- package/dist/{main-B561M1d3.mjs → main-BOPMYqsW.mjs} +2 -2
- package/dist/{main-B561M1d3.mjs.map → main-BOPMYqsW.mjs.map} +1 -1
- package/dist/{main-CCfc45ev.mjs → main-CiigNpsP.mjs} +7 -4
- package/dist/main-CiigNpsP.mjs.map +1 -0
- package/dist/{main-Ce9dcrsg.mjs → main-DeNFQ-UL.mjs} +6 -3
- package/dist/{main-Ce9dcrsg.mjs.map → main-DeNFQ-UL.mjs.map} +1 -1
- package/dist/{main-B-jwm8ED.mjs → main-DmCPcxOc.mjs} +2 -2
- package/dist/{main-B-jwm8ED.mjs.map → main-DmCPcxOc.mjs.map} +1 -1
- package/dist/{main-C45e7grq.mjs → main-DvgJSm11.mjs} +2 -2
- package/dist/{main-C45e7grq.mjs.map → main-DvgJSm11.mjs.map} +1 -1
- package/dist/{src-C-PfEDMY.mjs → src-vG79L-8m.mjs} +57 -26
- package/dist/src-vG79L-8m.mjs.map +1 -0
- package/drizzle/0002_wide_colleen_wing.sql +2 -0
- package/drizzle/0003_skinny_daimon_hellstrom.sql +1 -0
- package/drizzle/0004_pretty_bruce_banner.sql +1 -0
- package/drizzle/meta/0002_snapshot.json +1460 -0
- package/drizzle/meta/0003_snapshot.json +1460 -0
- package/drizzle/meta/0004_snapshot.json +1470 -0
- package/drizzle/meta/_journal.json +21 -0
- package/package.json +7 -1
- package/scripts/create-project.mjs +61 -0
- package/src/client.ts +412 -0
- package/src/config/index.ts +107 -0
- package/src/contracts/common.ts +28 -0
- package/src/contracts/envelope.ts +31 -0
- package/src/contracts/events/notification-ai-pending.ts +18 -0
- package/src/contracts/events/notification-canceled.ts +7 -0
- package/src/contracts/events/notification-created.ts +14 -0
- package/src/contracts/events/notification-delivered.ts +17 -0
- package/src/contracts/events/notification-dispatched.ts +45 -0
- package/src/contracts/events/notification-enriched.ts +46 -0
- package/src/contracts/events/notification-failed.ts +19 -0
- package/src/contracts/events/notification-requested.ts +36 -0
- package/src/contracts/events/notification-scheduled.ts +9 -0
- package/src/contracts/events/notification-skipped.ts +9 -0
- package/src/contracts/helpers.ts +21 -0
- package/src/contracts/index.ts +46 -0
- package/src/contracts/metadata.ts +10 -0
- package/src/contracts/registry.ts +88 -0
- package/src/contracts/sdk.ts +242 -0
- package/src/contracts/streams.ts +62 -0
- package/src/db/index.ts +69 -0
- package/src/db/schema.ts +412 -0
- package/src/idempotency/index.ts +50 -0
- package/src/index.ts +19 -0
- package/src/logger/index.ts +60 -0
- package/src/metrics/index.ts +53 -0
- package/src/queue/index.ts +501 -0
- package/src/rate-limiter/index.ts +210 -0
- package/src/redis/index.ts +89 -0
- package/src/repositories/index.ts +1246 -0
- package/src/server.ts +277 -0
- package/src/services/ai/main.ts +404 -0
- package/src/services/api/handlers.ts +1734 -0
- package/src/services/api/http.ts +64 -0
- package/src/services/api/main.ts +693 -0
- package/src/services/api/router.ts +82 -0
- package/src/services/delivery/main.ts +842 -0
- package/src/services/delivery/throttle.ts +71 -0
- package/src/services/engine/main.ts +827 -0
- package/src/services/enricher/main.ts +594 -0
- package/src/services/events/main.ts +365 -0
- package/src/services/scheduler/main.ts +319 -0
- package/src/services/workflow/main.ts +627 -0
- package/src/shared/batch-processor.ts +67 -0
- package/src/shared/cache.ts +47 -0
- package/src/shared/circuit-breaker.ts +74 -0
- package/src/shared/dataloader.ts +41 -0
- package/src/shared/events.ts +3 -0
- package/src/shared/index.ts +39 -0
- package/src/shared/semaphore.ts +33 -0
- package/src/shared/utils.ts +64 -0
- package/src/templates/cache.ts +32 -0
- package/src/templates/index.ts +69 -0
- package/src/templates/render.ts +128 -0
- package/src/transport/index.ts +96 -0
- package/src/unsubscribe/index.ts +127 -0
- package/src/workers/health.ts +31 -0
- package/src/workers/index.ts +266 -0
- package/src/workflows/index.ts +2 -0
- package/src/workflows/registry.ts +21 -0
- package/src/workflows/sdk.ts +106 -0
- package/dist/main-CAH0_Q6d.mjs.map +0 -1
- package/dist/main-CCfc45ev.mjs.map +0 -1
- 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
|
|
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
|
-
[](https://www.npmjs.com/package/notifkit) [](https://www.npmjs.com/package/notifkit) [](https://www.npmjs.com/package/notifkit) [](https://www.npmjs.com/package/notifkit) [](https://github.com/devkitshq/notifkit/actions/workflows/ci.yml) [](https://www.typescriptlang.org/) [](https://nodejs.org) [](./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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
42
|
+
## How it runs
|
|
53
43
|
|
|
54
|
-
notifkit is
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
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
|
-
|
|
102
|
+
### 2. Run the engine
|
|
137
103
|
|
|
138
|
-
|
|
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
|
-
|
|
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
|
-
|
|
122
|
+
await server.start();
|
|
123
|
+
console.log("notifkit listening on http://localhost:3000");
|
|
124
|
+
```
|
|
157
125
|
|
|
158
|
-
|
|
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
|
-
|
|
128
|
+
```bash
|
|
129
|
+
ADMIN_API_KEY=supersecretkey RESEND_API_KEY=re_xxx npx tsx server.ts
|
|
130
|
+
```
|
|
161
131
|
|
|
162
|
-
|
|
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
|
-
|
|
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
|
-
|
|
139
|
+
```bash
|
|
140
|
+
ADMIN_API_KEY=supersecretkey npx notifkit-create-project "my-app"
|
|
141
|
+
```
|
|
169
142
|
|
|
170
|
-
|
|
143
|
+
```
|
|
144
|
+
Project "my-app" created. Save the API key now — it is not recoverable.
|
|
171
145
|
|
|
172
|
-
|
|
173
|
-
|
|
146
|
+
NOTIFKIT_PROJECT_ID=1ce67fa1-b4a9-4985-8046-ef6018912b2a
|
|
147
|
+
NOTIFKIT_API_KEY=nk_live_f57c57b76d795cef89e2dbf6b6f352a36…
|
|
174
148
|
```
|
|
175
149
|
|
|
176
|
-
|
|
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
|
-
|
|
180
|
-
import {
|
|
157
|
+
// client.ts
|
|
158
|
+
import { NotifkitClient } from "notifkit";
|
|
181
159
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
188
|
+
With the server still running in the first terminal, run the client in a second one:
|
|
215
189
|
|
|
216
|
-
|
|
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.
|
|
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
|
-
|
|
232
|
-
|
|
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
|
-
|
|
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
|
|
299
|
+
Your application and your AI agents use the same notification infrastructure. Through MCP an agent can:
|
|
256
300
|
|
|
257
|
-
-
|
|
258
|
-
-
|
|
259
|
-
-
|
|
260
|
-
-
|
|
261
|
-
-
|
|
262
|
-
-
|
|
263
|
-
-
|
|
264
|
-
-
|
|
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
|
-
###
|
|
310
|
+
### The same task, with and without an agent
|
|
267
311
|
|
|
268
|
-
| Without an agent
|
|
269
|
-
|
|
|
270
|
-
|
|
|
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
|
-
[
|
|
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
|
|
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
|
|
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)
|
|
318
|
-
- [`@notifkit/provider-fcm`](./packages/provider-fcm)
|
|
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
|
|
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
|
|
364
|
+
// Send through SES, Postmark, APNs,
|
|
326
365
|
// SendGrid, a custom webhook, or anything else.
|
|
327
366
|
}
|
|
328
367
|
}
|
|
329
368
|
```
|
|
330
369
|
|
|
331
|
-
|
|
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
|
-
|
|
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).
|