notifyd
Servicio de notificaciones autoalojado (email, SMS, push, en la aplicación) en un solo binario de Rust sobre Postgres; su servidor MCP permite que un agente opere la instancia: resúmenes, trabajos, reintentos, supresiones, envíos de prueba.
Documentación
notifyd
El servicio de notificaciones que tu agente puede enviar a través de y en ejecución.
Correo electrónico, SMS, WhatsApp, push, bandeja de entrada en la aplicación. Un solo binario de Rust. Solo Postgres. Sin panel de control: un endpoint de resumen y un servidor MCP en su lugar.
Inicio rápido • Operaciones del agente • Referencia de la API • Arquitectura • Benchmarks • llms.txt • Contribuciones
Qué es
Todo producto necesita enviar correos electrónicos, mensajes de texto y notificaciones en la aplicación. Las opciones habituales son un SaaS alojado que se factura por notificación, o una pila autoalojada con MongoDB, Redis y cuatro contenedores detrás de un panel de React.
notifyd es la tercera opción: un único binario de 10 MB con PostgreSQL como única dependencia, que envía a través de los proveedores que ya tienes (Resend, Cloudflare Email Service, cualquier SMTP, AgentMail, Telnyx, Twilio, Web Push, FCM), con un motor de entrega real (prioridades, ritmo, reintentos con Retry-After, conmutación por error de proveedor, ventanas de envío, cancelación de suscripción con un clic) y sin interfaz de administración en absoluto. Operarlo es una llamada a la API o una herramienta MCP, por lo que la persona de guardia puede ser un agente de IA.
your app / your agent ──POST /v1/send──▶ notifyd ──▶ email · sms · whatsapp · push · in-app (SSE)
your agent ──POST /mcp─────▶ notifyd ──▶ digest · jobs · retries · suppressions · settings
Tres instancias se ejecutan en producción hoy, una por empresa, operadas de esta manera.

Explicación de 60 s, datos inventados. MP4 · Fuente de Remotion
Envía en una sola llamada
curl -X POST https://notifyd.example.com/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"},
"priority": "normal",
"idempotency_key": "report-42-ready"
}'
REST plano, curl es el SDK. Los reintentos son seguros (idempotency_key), la programación es un campo (scheduled_at), una campaña de marketing es POST /v1/batch con miles de suscriptores por llamada y aterriza en el carril masivo para que nunca retrase un restablecimiento de contraseña. Sigue cualquier envío con GET /v1/jobs/:id.
Deja que tu agente lo opere
La mayoría de las herramientas de notificación fueron diseñadas para un humano que hace clic en un panel. notifyd expone el trabajo del operador como herramientas, con el detalle que un operador humano necesitaría:
1. Una llamada dice qué necesita atención. GET /v1/admin/digest clasifica los hallazgos y te dice la acción para cada uno, en JSON o Markdown:
# notifyd digest — last 1d
Instance: commit e14e6f3, up 3d, email resend (+ smtp fallback), sms telnyx
## Findings
- **warning** — Primary email provider `resend` is resting for 47s after refusing messages; `smtp` is delivering.
_Nothing lost. Check the primary provider's status page; if it repeats, lower EMAIL_RATE_PER_SEC or move the primary role to the other provider._
- **warning** — Bounce rate 5.3 % over the window (14 bounced / 263 delivered).
_Above 5 % providers throttle or suspend the sender. Clean the recipient list; suppressions are applied automatically._
- **warning** — 3 job(s) failed in the last 1d (0.1 % of terminal jobs). Top cause: 422 unverified sender domain.
_Inspect with list_jobs(status=failed); permanent errors need a fix on the caller side, then retry_job._
## Queue pending 0, retry 2, processing 0
## Outcomes email/resend 4 812 sent, 3 failed · in_app 1 203 sent
## Latency email p50 0.6s, p95 2.1s (scheduled → accepted by provider)
## Deliverability delivered 4 790, bounced 14, complained 0, unsubscribed 9
2. Las mismas operaciones como herramientas MCP. POST /mcp es un servidor MCP Streamable HTTP (revisión actual de la especificación, initialize heredado conservado). Añádelo a Claude Code, Claude Desktop, Cursor o tu propio agente:
{ "mcpServers": { "notifyd": {
"type": "http", "url": "https://notifyd.example.com/mcp",
"headers": { "Authorization": "Bearer ${NOTIFYD_ADMIN_API_KEY}" } } } }
| Herramienta | Qué puede hacer el agente |
|---|---|
digest | Hallazgos clasificados con acciones, cola, resultados, latencia, entregabilidad, por proyecto |
list_jobs, get_job | Filtrar por proyecto, canal, estado, destinatario, tiempo; ver proveedor, intentos, último error |
retry_job, cancel_job | Actuar sobre un envío atascado o incorrecto |
list_projects, update_project | Identidad del remitente (from_email, from_name), canales, límite de velocidad entrante, send_window masivo en la zona horaria de los destinatarios |
list_suppressions, add_suppression, release_suppression | Lista de supresión con alcance all o marketing |
template_metrics | Enviados, fallidos, rebotados, abiertos por plantilla |
send_test | Probar el pipeline de extremo a extremo en cualquier canal |
Cada herramienta lleva anotaciones readOnlyHint / destructiveHint y un outputSchema. Una clave de operador de solo lectura (READONLY_API_KEY) expone solo las herramientas de lectura, para un agente que informa pero no debe actuar. Cada llamada MCP es auditada.
3. Todo lo que un agente necesita para integrarse está en el repositorio. docs/llms.txt es toda la API en texto plano para una ventana de contexto; tres Agent Skills se incluyen en skills/ (notifyd-operate, notifyd-integrate, notifyd-deploy):
npx skills add rmzlb/notifyd
Publicado en el registro oficial de MCP como mcp-name: io.github.rmzlb/notifyd (server.json). Guía completa del operador: docs/AGENT.md.
Características
Canales
- Correo electrónico — Resend, Cloudflare Email Service, AgentMail, cualquier SMTP (
lettre), adjuntos, identidad de remitente por proyecto - SMS — Telnyx o Twilio, cambia con una variable
- WhatsApp — Telnyx
- Push — Web Push (VAPID) o FCM
- Bandeja de entrada en la aplicación — REST + SSE en tiempo real (
EventSource), leer / archivar / destacar, insignia de no leídos, multi-réplica a través deNOTIFYde Postgres
Motor de entrega
- Prioridades — carriles
critical,normal,bulk; las etiquetas/v1/batchy de campaña aterrizan enbulk - Ritmo — cubo de tokens por canal (
EMAIL_RATE_PER_SEC); un 429 del proveedor pausa ese canal duranteRetry-Aftersin consumir un intento, otros canales siguen fluyendo, y cuando se reanuda el orden de reclamación ponecriticalprimero - Reintentos — 30 s → 2 m → 10 m → 30 m → 2 h con jitter, fallo rápido en 4xx, lotes rechazados caen elemento por elemento
- Conmutación por error — segundo proveedor de correo con un interruptor de circuito (
EMAIL_FALLBACK_PROVIDER) - Ventanas de envío — horas de silencio por proyecto, evaluadas en la zona horaria de cada suscriptor
- Programación, idempotencia, recolector de trabajos atascados, idempotencia de lotes
Gobernanza
- Cancelación de suscripción — RFC 8058
List-Unsubscribecon un clic en cada correo de marketing, alcances de supresiónall/marketing - Preferencias — opt-in / opt-out por suscriptor
- Multi-proyecto — una instancia, muchos proyectos, aislados por clave de API, rotación de claves con período de gracia
- Enmascaramiento de PII en registros, registro de auditoría de cada mutación, límite de velocidad por proyecto
Operaciones
- Resumen, servidor MCP, Agent Skills,
llms.txt - Métricas —
/v1/metrics,/v1/metrics/prometheus, métricas por plantilla - Webhooks — eventos de entrega a tus endpoints
- Flujos de trabajo — secuencias de varios pasos activadas por eventos, estado en Postgres
- Plantillas — sustitución
{{variable}}, almacenadas por proyecto
Inicio rápido
Docker (recomendado)
La imagen lee su configuración de variables de entorno; no hay archivo de configuración que montar.
git clone https://github.com/rmzlb/notifyd.git && cd notifyd
cat > .env <<'EOF'
JWT_SECRET=change-me-32-random-chars-minimum
ADMIN_API_KEY=change-me-32-random-chars-minimum
RESEND_API_KEY=re_xxx
EMAIL_FROM=notifications@yourdomain.com
EOF
docker compose up -d # notifyd + Postgres 16, http://localhost:3400
Crea un proyecto y obtén su clave de API:
curl -s -X POST http://localhost:3400/v1/admin/projects \
-H "X-Api-Key: $ADMIN_API_KEY" -H "Content-Type: application/json" \
-d '{"id":"myapp","name":"My app","channels":["email","in_app"],"from_email":"hello@yourdomain.com"}'
# → {"project": {"id": "myapp", "api_key": "sk_myapp_…", …}}
Imagen precompilada, linux/amd64 y linux/arm64: ghcr.io/rmzlb/notifyd.
Binario, crate, Nix o fuente
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/rmzlb/notifyd/releases/latest/download/notifyd-installer.sh | sh
brew install rmzlb/tap/notifyd # macOS and Linux, Homebrew
cargo binstall notifyd # prebuilt from the GitHub release
cargo install notifyd # build from crates.io
nix run github:rmzlb/notifyd # flake: packages, devShell, NixOS module
# then
DATABASE_URL=postgres://notifyd:pass@localhost:5432/notifyd \
JWT_SECRET=… ADMIN_API_KEY=… RESEND_API_KEY=… EMAIL_FROM=… notifyd
Binarios de lanzamiento: Linux x86_64 y aarch64, macOS Intel y Apple Silicon. NixOS: services.notifyd.enable = true; con un environmentFile (ver flake.nix).
¿Aún no tienes proveedor? EMAIL_PROVIDER=log imprime correos electrónicos en lugar de enviarlos.
Verificar
curl http://localhost:3400/v1/health
# → {"status":"ok","db":"ok","version":"0.2.2","commit":"…","uptime_seconds":12}
→ Configuración completa, alternativa TOML, notas de producción: docs/SETUP.md
API de un vistazo
Los endpoints de proyecto toman X-Api-Key: sk_<project>_…; los endpoints de operador toman la clave de administrador (o de solo lectura), como X-Api-Key o Authorization: Bearer. Los endpoints de bandeja de entrada también aceptan un JWT de suscriptor.
| Método | Endpoint | Qué hace |
|---|---|---|
POST | /v1/send | Enviar en uno o varios canales |
POST | /v1/batch | Enviar a muchos suscriptores (carril masivo, idempotente) |
GET | /v1/jobs/:id | Estado, proveedor, intentos, último error |
GET | /v1/inbox/:id · /stream | Bandeja de entrada en la aplicación, flujo SSE en tiempo real |
POST | /v1/workflows/trigger | Activar un flujo de trabajo basado en eventos |
GET | /v1/admin/digest | Qué necesita atención, con acciones |
GET | /v1/admin/jobs · POST …/:id/retry | Vista y acciones del operador |
PATCH | /v1/admin/projects/:id | Remitente, canales, límite de velocidad, ventana de envío |
POST | /mcp | Servidor MCP (Streamable HTTP) |
GET | /v1/metrics/prometheus | Exposición de Prometheus |
GET | /u/:token | Página de cancelación de suscripción con un clic |
→ Cada endpoint con ejemplos: docs/API.md, o alimenta docs/llms.txt a tu agente.
vs. las alternativas
| Novu | Knock / Courier / SuprSend | notifyd | |
|---|---|---|---|
| Infraestructura | MongoDB + Redis + 4 contenedores | SaaS alojado | Solo Postgres, una imagen de 42 MB |
| Configuración | 30+ min | Registro + panel | docker compose up (2 min) |
| Lenguaje | Node.js (múltiples servicios) | N/A (alojado) | Rust (binario único) |
| Memoria | no medido por nosotros | N/A | 13 MB en reposo, 23 MB drenando 100k trabajos (método) |
| Rendimiento | — | limitado por cuota | 44k trabajos/s en cola, 3.5k trabajos/s drenados (benchmarks) |
| 429 del proveedor | el trabajo falla | gestionado | canal pausado durante Retry-After, intento no consumido, proveedor de conmutación probado primero |
| Prioridades / ventanas de envío | ❌ | ✅ | ✅ carriles crítico → masivo, ventanas por zona horaria del suscriptor |
| Superficie de operaciones | panel de React | panel + API | endpoint de resumen, servidor MCP, Agent Skills, Prometheus |
| Tiempo real | WebSocket | WebSocket | SSE, EventSource nativo, multi-réplica |
| Autoalojado | ✅ (pesado) | ❌ | ✅ un contenedor por empresa |
| Costo | Nivel gratuito / de pago | por notificación | Gratis para siempre, MIT |
Solo publicamos números que medimos en notifyd mismo; el resto de la tabla describe la forma, no el rendimiento. Método, hardware y descargo de sesgo en docs/BENCHMARKS.md.
Bandeja de entrada en la aplicación y SDK de TypeScript
import { createNotifydClient } from 'notifyd-sdk'; // pnpm add notifyd-sdk@github:rmzlb/notifyd
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' } });
// Browser: subscriber token from your backend, then a plain EventSource
const events = new EventSource(`${url}/v1/inbox/${userId}/stream?token=${jwt}`);
events.onmessage = (e) => { const d = JSON.parse(e.data);
if (d.type === 'new_notification') showToast(d.notification);
if (d.type === 'count_update') updateBadge(d.unread_count); };
Flujos de trabajo
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": "condition", "check": "completed_onboarding",
"if_false": [{"type": "send", "channel": "email", "template": "nudge"}]}
]}'
El estado vive en Postgres y sobrevive a los reinicios.
Configuración
Las variables de entorno son la interfaz principal (eso es lo que usan la imagen y el archivo compose); se acepta un notifyd.toml para desarrollo local. Requerido: DATABASE_URL, JWT_SECRET, ADMIN_API_KEY. Luego un proveedor:
| Variable | Propósito |
|---|---|
EMAIL_PROVIDER | resend (predeterminado cuando RESEND_API_KEY está configurado), cloudflare, smtp, agentmail, log |
EMAIL_FROM, EMAIL_FROM_NAME | Remitente predeterminado de la instancia; los proyectos pueden anular |
EMAIL_FALLBACK_PROVIDER | Segundo proveedor en 429 / 5xx |
EMAIL_RATE_PER_SEC | Ritmo de salida por réplica |
SMS_PROVIDER, SMS_FROM | telnyx o twilio con sus credenciales |
PUBLIC_URL | URL base para enlaces de cancelación de suscripción con un clic |
READONLY_API_KEY | Clave de operador de solo lectura opcional |
→ Cada variable, por proveedor: docs/CONNECTORS.md y docker-compose.yml. Referencia TOML: notifyd.toml.example.
Arquitectura
┌──────────────────────── notifyd (one binary) ───────────────────────┐
HTTP /v1, /mcp ─▶ axum API ─▶ jobs table ─▶ worker: claim (SKIP LOCKED, by priority) │
│ ├─ pacer per channel, channel pause on 429│
│ ├─ connectors (email/sms/whatsapp/push/in-app)
│ ├─ failover breaker, retries, reaper │
│ └─ webhooks, metrics, audit │
│ SSE hub ◀── Postgres NOTIFY ── (any replica) │
└───────────────────────────────┬──────────────────────────────────────┘
▼
PostgreSQL 16
src/
├── api/ # routes: send, batch, jobs, inbox, subscribers, templates, workflows, webhooks, admin ops, health
├── connectors/ # email (resend, cloudflare, smtp, agentmail, log), sms, whatsapp, push, in_app
├── worker.rs # claim by priority, batch context, finalize, retries, failover
├── pacing.rs # token buckets and channel pauses
├── failover.rs # provider circuit breaker
├── ops.rs # digest, findings, operator actions
├── mcp.rs # MCP server (tools, annotations, audit)
├── send_window.rs # quiet hours in the subscriber's timezone
├── unsubscribe.rs # List-Unsubscribe tokens and landing
├── sse.rs # inbox stream, Postgres NOTIFY fan-out
├── workflow_engine.rs, templates.rs, webhooks.rs, deliverability.rs, metrics.rs, pii.rs, middleware.rs
migrations/ # SQL, applied at start-up
skills/ # Agent Skills: operate, integrate, deploy
server.json # MCP registry entry
flake.nix # Nix package, devShell, NixOS module
dist-workspace.toml # cargo-dist: release binaries and installer
~12 000 líneas de Rust, sin unsafe. Binario de 10.8 MB, imagen de 42 MB, 13 MB RSS en reposo, 23 MB mientras drena 100 000 trabajos. → docs/ARCHITECTURE.md
Estado
notifyd es un 0.x usado en producción por sus autores. Lo que aún no hace: sin panel (por diseño), sin pruebas A/B, sin APNs, sin análisis de correo entrante, sin facturación multi-tenant. Los cambios importantes se anuncian en las notas de versión; el esquema de la cola se migra automáticamente.
Documentación
| 📦 Configuración | Desarrollo local, Docker, producción |
| 🔌 Referencia de API | Cada endpoint con ejemplos en curl / TypeScript / Rust |
| 🤝 Operaciones de agentes | Resumen, herramientas MCP, clave de solo lectura, cómo un agente ejecuta una instancia |
| 🔌 Conectores | Proveedores, variables de entorno, cómo añadir uno |
| 🏗️ Arquitectura | Cola, prioridades, ritmo, SSE, conectores |
| 📈 Benchmarks | Huella, rendimiento, cómo reproducirlos |
| 🚀 Despliegues | Una instancia por empresa, runbook |
| 📝 Escritura | Una cola de notificaciones solo con PostgreSQL: lo que SKIP LOCKED no te da |
| 📣 Visibilidad | Registros y canales de lanzamiento |
| 🤖 llms.txt | La API en texto plano para agentes |
Contribuciones
Las incidencias y las solicitudes de extracción son bienvenidas; good first issue es el lugar para
empezar, y las grandes funciones comienzan con una incidencia. Lee CONTRIBUTING.md.
git clone https://github.com/YOUR_USERNAME/notifyd.git && cd notifyd
cargo test && EMAIL_PROVIDER=log DATABASE_URL=… JWT_SECRET=dev ADMIN_API_KEY=dev cargo run
Licencia
MIT.
Construido con 🦀 en Grenoble, Francia 🏔️