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

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.

License Container image crates.io CI Image size Memory MCP registry Agent Skills

Inicio rápidoOperaciones del agenteReferencia de la APIArquitecturaBenchmarksllms.txtContribuciones


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.

60-second explainer: one send call, priority order under a provider 429, an agent operating the instance over MCP
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}" } } } }
HerramientaQué puede hacer el agente
digestHallazgos clasificados con acciones, cola, resultados, latencia, entregabilidad, por proyecto
list_jobs, get_jobFiltrar por proyecto, canal, estado, destinatario, tiempo; ver proveedor, intentos, último error
retry_job, cancel_jobActuar sobre un envío atascado o incorrecto
list_projects, update_projectIdentidad 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_suppressionLista de supresión con alcance all o marketing
template_metricsEnviados, fallidos, rebotados, abiertos por plantilla
send_testProbar 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 de NOTIFY de Postgres

Motor de entrega

  • Prioridades — carriles critical, normal, bulk; las etiquetas /v1/batch y de campaña aterrizan en bulk
  • Ritmo — cubo de tokens por canal (EMAIL_RATE_PER_SEC); un 429 del proveedor pausa ese canal durante Retry-After sin consumir un intento, otros canales siguen fluyendo, y cuando se reanuda el orden de reclamación pone critical primero
  • 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-Unsubscribe con un clic en cada correo de marketing, alcances de supresión all / 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étodoEndpointQué hace
POST/v1/sendEnviar en uno o varios canales
POST/v1/batchEnviar a muchos suscriptores (carril masivo, idempotente)
GET/v1/jobs/:idEstado, proveedor, intentos, último error
GET/v1/inbox/:id · /streamBandeja de entrada en la aplicación, flujo SSE en tiempo real
POST/v1/workflows/triggerActivar un flujo de trabajo basado en eventos
GET/v1/admin/digestQué necesita atención, con acciones
GET/v1/admin/jobs · POST …/:id/retryVista y acciones del operador
PATCH/v1/admin/projects/:idRemitente, canales, límite de velocidad, ventana de envío
POST/mcpServidor MCP (Streamable HTTP)
GET/v1/metrics/prometheusExposición de Prometheus
GET/u/:tokenPá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

NovuKnock / Courier / SuprSendnotifyd
InfraestructuraMongoDB + Redis + 4 contenedoresSaaS alojadoSolo Postgres, una imagen de 42 MB
Configuración30+ minRegistro + paneldocker compose up (2 min)
LenguajeNode.js (múltiples servicios)N/A (alojado)Rust (binario único)
Memoriano medido por nosotrosN/A13 MB en reposo, 23 MB drenando 100k trabajos (método)
Rendimientolimitado por cuota44k trabajos/s en cola, 3.5k trabajos/s drenados (benchmarks)
429 del proveedorel trabajo fallagestionadocanal 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 operacionespanel de Reactpanel + APIendpoint de resumen, servidor MCP, Agent Skills, Prometheus
Tiempo realWebSocketWebSocketSSE, EventSource nativo, multi-réplica
Autoalojado✅ (pesado)✅ un contenedor por empresa
CostoNivel gratuito / de pagopor notificaciónGratis 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:

VariablePropósito
EMAIL_PROVIDERresend (predeterminado cuando RESEND_API_KEY está configurado), cloudflare, smtp, agentmail, log
EMAIL_FROM, EMAIL_FROM_NAMERemitente predeterminado de la instancia; los proyectos pueden anular
EMAIL_FALLBACK_PROVIDERSegundo proveedor en 429 / 5xx
EMAIL_RATE_PER_SECRitmo de salida por réplica
SMS_PROVIDER, SMS_FROMtelnyx o twilio con sus credenciales
PUBLIC_URLURL base para enlaces de cancelación de suscripción con un clic
READONLY_API_KEYClave 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ónDesarrollo local, Docker, producción
🔌 Referencia de APICada endpoint con ejemplos en curl / TypeScript / Rust
🤝 Operaciones de agentesResumen, herramientas MCP, clave de solo lectura, cómo un agente ejecuta una instancia
🔌 ConectoresProveedores, variables de entorno, cómo añadir uno
🏗️ ArquitecturaCola, prioridades, ritmo, SSE, conectores
📈 BenchmarksHuella, rendimiento, cómo reproducirlos
🚀 DesplieguesUna instancia por empresa, runbook
📝 EscrituraUna cola de notificaciones solo con PostgreSQL: lo que SKIP LOCKED no te da
📣 VisibilidadRegistros y canales de lanzamiento
🤖 llms.txtLa 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 🏔️