docs /Notifications

Notifications

Everything ply does is already a line in the events journal, and ply reconcile already runs every minute on a host set up with sudo ply setup --edge. Notifications ride that beat: each minute ply reads the events since last time, keeps the ones you subscribed to, and sends a short line where you say. No new resident process, no metrics stack.

The premise for a one-to-five-server host: you are not going to run Prometheus and Alertmanager, but you do want to know when a deploy failed at 3am.

#Turn it on

One file, /var/lib/ply/notify.toml:

on = ["deploy-failed", "restart-loop", "snapshot-failed", "disk-high"]
to = ["telegram:<bot-token>:<chat-id>"]

on is the events to notify about; to is where. That's it — the reconcile timer picks it up. Flush immediately by hand with ply notify, and prove a destination works with ply notify --test.

#What you can subscribe to

Journal events, by name:

name fires when
deploy-failed a rolling deploy's health gate failed and reverted
deploy a deploy completed (noisy; usually you want only the failures)
instance-restart an instance crashed and was respawned (every crash)
restart-loop 3 crashes of one app within 5 minutes — one message, not one per crash
snapshot-failed a ply snapshot failed
snapshot / restore a snapshot was taken / a volume restored
egress-blocked an app under --egress enforce tried a destination it may not reach
disk-high the ply data filesystem passed 90% full (re-warns every 6h while high)

restart-loop and disk-high are computed, not raw journal lines: restart-loop fires once on the crash that crosses the threshold, so a wedged app sends one alert rather than a stream; disk-high is checked each beat. Subscribe to instance-restart instead if you truly want every crash.

#Where it can send

destination form
Telegram telegram:<bot-token>:<chat-id> (the token keeps its own colon; the chat id is the last field)
Discord discord:<webhook-url>
any webhook https://… (or webhook:https://…) — receives the message as a JSON string
a command command:<program> [args] — the message on its stdin

The command: form is the escape hatch: command:/usr/bin/mail -s ply ops@example.com sends email through a configured mail, and anything that reads stdin works the same way.

#Keep the token out of the repo

A bot token is a secret, and a fleet repo is at its best public. Seal the whole destination (Sealed secrets) under the name notify:

ply secret seal notify=telegram:123456:ABC-DEF:987654 --for ply-host-…
# notify = "enc:v1:…"

Put the enc:v1:… value in to. ply opens it with the host key when it sends, and never logs it:

on = ["deploy-failed", "restart-loop", "disk-high"]
to = ["enc:v1:9hf2…"]

#What it is not

No severities, silences, escalation or on-call rotations. That is what Prometheus and Alertmanager are for, and ply hands off to them rather than becoming them. This is one honest message per event you asked about, to a place you already read. Point a https:// destination at your own bridge if you want it fanned into something bigger.