Star
Start here

Why notifkit

notifkit is a self-hosted notification engine. Your app makes one call; notifkit decides who to reach, on which channel, with what content and when, and retries until the message lands or provably fails.

The problem it solves

Sending one notification is easy: take a Resend or Twilio API key and call send(). Making that call correct is the hard part.

One user opted out of SMS last week. Another is asleep in Tokyo. A third has three devices, two with dead push tokens. A worker crashed mid-batch and you can't tell which half went out. Marketing wants a reminder three days after signup, unless the invite was accepted. Each is a small change; together they turn send() into a distributed state machine you never planned to build.

notifkit is that state machine. You decide what to say; it handles getting it there.

What a send actually goes through

Every request passes a fixed sequence of gates before it reaches a provider. Each gate can drop, defer or pass the notification:

dedupe duplicate opt-out topic off channel channel off quiet hrs deferred template not found contact no address suppressed opted out throttle too many dispatch → provider notify() delivered
Red exits end delivery on that channel and emit a notification:skipped event with a reason. The amber exit is a deferral: a quiet-hours hit is rescheduled to the end of the window, not dropped. priority: "critical" bypasses quiet hours and the throttle, but never the suppression gate, which records unsubscribes, complaints and dead addresses.

What notifkit gives you

ConcernHow notifkit handles it
Targeting Send to a user id, an inline user object, an array of users, a segment, or a topic. Fan-out happens server-side.
Preferences Per-channel and per-topic opt-outs plus quiet-hours windows live on the user record and are enforced before rendering.
Consent One-click unsubscribe headers on topic-bearing mail. Unsubscribes, spam complaints, and hard bounces suppress the address on every later send.
Reporting Tag a send with a campaign label and read back delivered, opened, clicked, bounced, complained, and unsubscribed, with rates.
Channels email, sms, push, slack, whatsapp, telegram, discord, webhook. Send to several at once, or chain them as an ordered fallback.
Durability Redis Streams with consumer groups. A worker that dies mid-job leaves the message unacked; another worker reclaims it.
Deduplication Idempotency guards at the enricher, engine, and delivery stages. A request's x-idempotency-key is honoured for 24 hours.
Scheduling sendAt for future delivery, cancellable until dispatch. Quiet-hours deferral uses the same machinery.
Workflows Multi-step sequences that can wait("3d") or waitForEvent("invite.accepted") and survive process restarts.
Providers A Transport interface. First-party packages for Resend, FCM, Twilio, Slack, WhatsApp, Telegram and Discord, plus a console transport for local development.
Multi-tenancy Every row is scoped to a project. API keys are project-scoped and hashed at rest.
Observability Prometheus metrics, structured logs, per-message delivery history in Postgres, and a dead-letter queue you can inspect and replay.

What it is not

You run it. There's no hosted version; you need your own PostgreSQL database and Redis instance.

A campaign is a label on a send, for reporting afterwards. There's no campaign builder, visual editor, A/B testing or audience segmentation UI. If you need marketing automation, this is the wrong tool: it's infrastructure for product notifications that reports honestly on what it delivered.

notifkit delivers nothing itself. It drives Resend, FCM, Twilio or whatever you plug in.

Delivery is at-least-once. If a provider accepts a message but the response times out, notifkit retries and the user may see it twice. Exactly-once isn't possible across a remote provider API without two-phase commit, so tools that advertise it are describing their own deduplication, not what reached the user. notifkit deduplicates at four stages too, but doesn't call that a guarantee. See delivery guarantees.

Where to go next