notifyd

Logo

Agent-first notification service. One Rust binary, Postgres only. Email, SMS, WhatsApp, push, in-app inbox, MCP server built in.

View the Project on GitHub rmzlb/notifyd

Deployments — one notifyd per company

notifyd is deployed once per company, always from this repository, always from main, through a Dokploy Docker Compose service that points at ./docker-compose.yml. Everything company-specific lives in the Dokploy environment of that service; nothing company-specific lives in this repo. No secret value is ever written here, only variable names.

Keys since migration 020. The database stores only SHA-256 hashes of project keys. A lost key cannot be read back from the database: issue a new one with POST /v1/admin/projects/:id/rotate-key, update the application, then revoke-secondary.

Why per company, not one shared instance

Inventory (verified 2026-09-05)

Company Server (Dokploy alias) Dokploy service Public URL Notes
CRAIE CRAIE’s own server (own Dokploy) compose from this repo, autodeploy main notifyd.craie.ctrlnz.com Dedicated instance, redeploys within ~5 min of a push to main.
sqare + helmai square compose novu/notifyd-stack notifyd.ctrlnz.com Shared by two companies. Global sender is Helmai; the sqare project has its own from_email. Contains a stale craie project (created 2026-06, no key hash, zero jobs): delete it. An orphan Application notifyd/notifyd (2026-03) exists on the same server; its autodeploy is now off, delete it.
Philoé philoe compose notifyd (git source, autodeploy main via a GitHub webhook on this repo) internal http://notifyd:3400; public https://api-os.philoeparis.com/notifyd (path route on the API host, stripPath) for the admin in-app inbox Created 2026-09-05. Path route instead of a dedicated hostname because no Cloudflare DNS token was available; switch to notifyd-os.philoeparis.com when one exists. Project philoe, from_email in philoeparis.fr.

All instances use the same Resend team today (every company domain is verified there). A per-company Resend team would need a per-instance RESEND_API_KEY, which this layout already allows.

Keep this table current: it is the only place that says which instance serves which company.

Creating an instance (Dokploy)

  1. Project → Create ServiceCompose. Provider: GitHub, repository rmzlb/notifyd, branch main, compose path ./docker-compose.yml, Autodeploy on.
  2. Environment (names; generate secrets with openssl rand -hex 32): POSTGRES_PASSWORD, JWT_SECRET, ADMIN_API_KEY, RESEND_API_KEY (the company’s Resend team), EMAIL_FROM, EMAIL_FROM_NAME, CORS_ORIGINS (the company’s back-office origin), PORT=3400, RUST_LOG=notifyd=info. Optional: RESEND_WEBHOOK_SECRET (only if this instance, and not the application, is the consumer of the Resend webhooks), push/SMS connector variables.
  3. Deploy. docker compose attaches the container to dokploy-network under the alias notifyd, so applications on the same server reach it at http://notifyd:3400 without leaving the host. Add a public domain in Dokploy only if browsers must reach it (in-app inbox over SSE).
  4. Create the company project, from the server (docker exec into the container, or any host that can reach the instance):

    curl -s -X POST "$NOTIFYD_URL/v1/admin/projects" \
      -H "x-api-key: $ADMIN_API_KEY" -H 'content-type: application/json' \
      -d '{"id":"<company>","name":"<Company>","channels":["email","in_app"],
           "from_email":"<sender@company-domain>","from_name":"<Company>"}'
    

    The response carries the project API key: it goes into the application’s NOTIFYD_API_KEY, nowhere else.

  5. Check: GET /v1/health returns status: ok, the current commit, and a fresh built_at_epoch; GET /v1/admin/projects lists the project with its from_email.

Two things every new instance needs

Observability

GET /v1/metrics/prometheus (admin key as Authorization: Bearer) exposes outcomes, provider errors by kind, lane pauses, provider latency, queue depth and the age of the oldest waiting job per priority band. Point the Dokploy monitoring stack (or any Prometheus) at each instance. /v1/health carries commit and built_at_epoch for drift checks.

Rules that keep N instances safe