Prefect
A deployment with no worker on its work pool goes Late, not Failed, and Late sends nothing by default.
Prefect's notifications fire on Failed and Crashed flow runs. A deployment whose work pool has no worker polling it does not fail: its runs sit in Late, indefinitely, and Late is not a notification state unless you build an automation for it. The same silence covers a flow.serve() process that died, a schedule someone paused in the UI, and a deployment that was never re-applied after a redeploy.
State hooks on the flow
import urllib.request
from prefect import flow
PING = "https://vivere.dev/p/<monitor-id>"
def ping(suffix="", body=None):
try:
urllib.request.urlopen(PING + suffix, data=body, timeout=10)
except Exception:
pass
@flow(
name="nightly-etl",
on_completion=[lambda flow, run, state: ping()],
on_failure=[lambda flow, run, state: ping("/fail", str(state).encode())],
)
def nightly_etl():
...
Hooks are called with (flow, flow_run, state). Put them on the flow rather than on tasks, so one monitor tracks one deployment.
Or report around the work itself
@flow
def nightly_etl():
ping("/start")
try:
rows = extract()
load(rows)
except Exception as exc:
ping("/fail", str(exc).encode())
raise
ping("", f"loaded {len(rows)} rows".encode())
The start ping gives you the run's duration in Vivere, and the text body becomes the run log, so a late night's "what did it actually do" does not need the Prefect UI.
Monitor settings
Match the deployment's schedule: an interval schedule of 30 minutes is period 30m, a cron schedule goes in as a cron expression with the deployment's timezone. Prefect starts a run when a worker next polls, so add the poll interval to the grace on top of the flow's worst normal run.