Agent-first notification service. One Rust binary. Postgres only. No Redis, no Mongo, no nonsense.
Quick Start β’ API Reference β’ Setup Guide β’ Architecture β’ LLM Docs β’ Contributing
Your AI agent needs to send an email. Or a push notification. Or update an in-app inbox.
You look at Novu: MongoDB, Redis, 4 containers, a React SDK, 30 minutes of setup. Your agent doesn't care about any of that. It just wants to POST /v1/send and move on.
notifyd is what that looks like. A single Rust binary. One POST call. Your agent sends notifications and gets back to work.
Agent ββPOST /v1/sendβββ notifyd βββ Email (Resend)
βββ SMS (Twilio/Telnyx)
βββ Push (FCM)
βββ In-App (SSE)
Most notification services were designed for humans clicking buttons in a dashboard. notifyd was designed for agents making API calls.
Flat REST API β no SDK needed, no WebSocket handshake, no complex auth flows. curl works. Your agent's HTTP client works.
docs/llms.txt β the entire API reference in plain text, optimized for LLM context windows. Point your agent at it and it can call any endpoint. (View it)
Idempotency built-in β agents retry. That's fine. Pass idempotency_key and notifyd deduplicates.
One binary, one config file β docker compose up and you have a notification service. No infra degree required.
# Your agent sends a notification. That's it.
curl -X POST http://localhost:3400/v1/send \
-H "X-Api-Key: sk_myapp_xxx" \
-H "Content-Type: application/json" \
-d '{
"channels": ["email", "in_app"],
"subscriber_id": "user-1",
"subject": "Your report is ready",
"body": "Hey {{first_name}}, the analysis you requested is complete.",
"vars": {"first_name": "Alice"},
"idempotency_key": "report-42-ready"
}'Feed docs/llms.txt to any LLM agent and it can operate the full API:
https://raw.githubusercontent.com/rmzlb/notifyd/main/docs/llms.txt
Or describe notifyd as a tool:
{
"name": "send_notification",
"description": "Send email/SMS/push/in-app via notifyd",
"endpoint": "POST /v1/send",
"auth": "X-Api-Key header"
}| Novu | Knock | notifyd | |
|---|---|---|---|
| Infra | MongoDB + Redis + 4 containers | Hosted SaaS | Postgres only |
| Setup | 30+ min | Signup + dashboard | docker compose up (2 min) |
| Language | Node.js (multiple services) | N/A (hosted) | Rust (single binary) |
| Memory | ~800MB+ | N/A | ~15MB |
| Agent-friendly | SDK-heavy | Dashboard-first | REST-first, llms.txt included |
| Realtime | WebSocket | WebSocket | SSE (simpler, works everywhere) |
| Self-hosted | β (heavy) | β | β (one container) |
| Queue | Redis + BullMQ | Managed | Postgres SKIP LOCKED |
| Cost | Free tier / paid | $0.01/notification | Free forever |
- π§ Email β via Resend (plug your API key)
- π± SMS β Twilio or Telnyx (swap in config, zero code change)
- π Push β FCM (Firebase Cloud Messaging)
- π¬ In-app inbox β REST + realtime SSE stream
- β° Scheduling β
scheduled_aton any notification - π Retry β exponential backoff (30s β 2min β 10min)
- π Idempotency β safe agent retries
- π Templates β
{{variable}}substitution, stored per project - π’ Multi-project β one instance, many projects, isolated by API key
- β‘ Workflows β event-triggered multi-step sequences
- π€ Preferences β per-subscriber opt-in/opt-out
- π Audit log β every mutation logged
- π¦ Rate limiting β per-project sliding window
- π Metrics β
/v1/metricsfor monitoring - πͺ Webhooks β delivery events to your endpoints
git clone https://github.com/rmzlb/notifyd.git && cd notifyd
cp notifyd.toml.example notifyd.toml
# Edit notifyd.toml β add your Resend API key at minimum
docker compose up -d
# β notifyd running on http://localhost:3400# Rust 1.75+, PostgreSQL 16+
git clone https://github.com/rmzlb/notifyd.git && cd notifyd
cp notifyd.toml.example notifyd.toml
cargo runcurl http://localhost:3400/v1/health
# β {"status":"ok","db":"ok","version":"0.1.0"}β Full setup: docs/SETUP.md
Every endpoint uses X-Api-Key: sk_<project>_xxx. Inbox endpoints also accept subscriber JWT.
| Method | Endpoint | What it does |
|---|---|---|
POST |
/v1/send |
Send notification (email, SMS, push, in-app) |
POST |
/v1/batch |
Send to multiple subscribers |
GET |
/v1/inbox/:id |
List in-app notifications |
GET |
/v1/inbox/:id/stream |
SSE realtime stream |
POST |
/v1/workflows/trigger |
Trigger event-based workflow |
GET |
/v1/health |
Health check |
GET |
/v1/metrics |
Service metrics |
β Full reference: docs/API.md β or feed docs/llms.txt to your agent.
A small official SDK now ships in this repo for backend + frontend apps.
pnpm add notifyd-sdk@github:rmzlb/notifydimport { createNotifydClient } from 'notifyd-sdk';
const notifyd = createNotifydClient({
url: process.env.NOTIFYD_URL!,
apiKey: process.env.NOTIFYD_API_KEY!,
});
await notifyd.send({
channels: ['email', 'in_app'],
subscriberId: 'user-123',
subject: 'Your report is ready',
body: 'Hey {{first_name}}, the analysis is complete.',
vars: { first_name: 'Alice' },
});
const token = await notifyd.createSubscriberToken({
subscriberId: 'user-123',
ttlHours: 8,
});It wraps the REST API with typed helpers for send, subscribers, inbox, unread count, mark read, and SSE stream setup.
Complete notification inbox with realtime SSE. No WebSocket library, no Redis pub/sub β just native EventSource.
// Connect to realtime stream
const events = new EventSource(
`https://notifyd.example.com/v1/inbox/${userId}/stream?token=${jwt}`
);
events.onmessage = (e) => {
const data = JSON.parse(e.data);
if (data.type === 'new_notification') showToast(data.notification);
if (data.type === 'count_update') updateBadge(data.unread_count);
};Features: read/unread, archive, todo/star, pagination, unread count badge, realtime push.
Multi-step notification sequences triggered by events:
curl -X POST http://localhost:3400/v1/workflows \
-H "X-Api-Key: sk_myapp_xxx" \
-d '{
"id": "welcome-series",
"trigger_event": "user.signup",
"steps": [
{"type": "send", "channel": "email", "template": "welcome"},
{"type": "delay", "duration": "24h"},
{"type": "send", "channel": "email", "template": "getting_started"},
{"type": "delay", "duration": "72h"},
{"type": "condition", "check": "completed_onboarding", "if_false": [
{"type": "send", "channel": "email", "template": "nudge"}
]}
]
}'State persisted in Postgres β survives restarts. No in-memory state to lose.
Single TOML file. Minimal setup:
[server]
port = 3400
jwt_secret = "your-secret-here"
[database]
url = "postgres://notifyd:pass@localhost:5432/notifyd"
[connectors.email]
provider = "resend"
api_key = "re_xxx"
from = "notifications@yourdomain.com"
[projects.myapp]
api_key = "sk_myapp_xxx"
channels = ["email", "in_app"]β Full config: notifyd.toml.example
notifyd/
βββ src/
β βββ main.rs # Server bootstrap, graceful shutdown
β βββ config.rs # TOML config
β βββ db.rs # sqlx models
β βββ worker.rs # Background job processor
β βββ workflow_engine.rs # Event-driven workflows
β βββ sse.rs # SSE broadcaster (tokio channels)
β βββ templates.rs # {{var}} engine
β βββ pii.rs # PII masking for logs
β βββ middleware.rs # Rate limiter + audit
β βββ api/ # 13 route modules
β βββ connectors/ # Email, SMS, Push, In-App
βββ migrations/ # 4 SQL migrations (auto-run)
βββ Dockerfile # Multi-stage + cargo-chef
βββ docker-compose.yml # notifyd + Postgres
βββ notifyd.toml.example # Config reference
βββ docs/ # API ref, setup, architecture, llms.txt
~3,700 lines of Rust. Every file < 400 lines.
| π¦ Setup Guide | Local dev, Docker, production deploy |
| π API Reference | Every endpoint with curl/TS/Rust examples |
| ποΈ Architecture | Queue design, SSE internals, connectors |
| π€ LLM Docs | Full API in plain text β feed to your agent |
notifyd is built in Grenoble, in the French Alps ποΈ β but open to contributors from everywhere.
- Read the Contributing Guide
- Check open issues β
good first issueis a great start - Big features β open an issue first
git clone https://github.com/YOUR_USERNAME/notifyd.git
cd notifyd && cp notifyd.toml.example notifyd.toml
cargo test && cargo runMIT β use it however you want.
Built with π¦ in Grenoble, France ποΈ