Notification delivery is at-most-once — failed dispatches are never retried (design + outbox) #50
Labels
No labels
area:ai
area:ci-cd
area:notifications
area:observability
area:public-pages
backlog
bug
duplicate
enhancement
help wanted
invalid
question
type:decision
type:feature
type:infra
type:maintenance
type:security
v1.0.1
v1.1.0
v1.2.0
v1.3.0
v2.0.0
wontfix
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
rbrooks/WeatherBot#50
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
All three dispatch paths commit the dedup
SentAlertbefore attemptingdelivery (app/services/spc_processor.py:481-547 and :568-609;
app/services/alert_processor.py:656-659 fire-and-forget). A transient channel
failure is logged/recorded but the committed dedup row suppresses any retry —
the notification is permanently dropped. Decide the delivery semantics, then
implement:
Option A (recommended): delivery-outbox table keyed (alert record, channel)
with status/attempts/last_error; scheduler job retries pending/failed with
backoff until a max age; dispatch tasks tracked (fixes the GC-able
fire-and-forget tasks too). Option B (minimal): on failure, mark the
per-channel dedup row retryable with an attempts cap.
Rationale for scheduling with v1.2.0: the formatter migration (#14) already
touches every notifier; doing the dispatch-semantics change in the same wave
avoids rewriting the notifier boundary twice. Pull earlier if the silent-drop
rate observed in diagnostics warrants it.
Acceptance criteria:
Filed from the 2026-07-17 codebase audit (
docs/.internal/report-2026-07-17.md), finding F-11.Done in #100 (merged). Delivery is now at-least-once for the NWS warning path: a
notification_deliveriesoutbox (migration 0020) records per-(alert, channel, kind) attempts; dispatch commits a durablependingrow and sends inline, and aprocess_delivery_outboxjob retriesfailedrows with exponential backoff (FOR UPDATE SKIP LOCKED) up toDELIVERY_MAX_ATTEMPTS/DELIVERY_MAX_AGE_HOURSbeforedead. Crucially, the notifiers now raiseTransientDeliveryError/PermanentDeliveryErrorinstead of swallowing HTTP errors, so the outbox actually observes and retries real transient failures (asentrow is never re-sent). Acceptance verified end-to-end: transient failure → later successful delivery; permanent → dead; no duplicates. SPC/MCD sends are recorded to the ledger for observability; SPC auto-retry is a documented follow-up (entangled with impact-page/AI side effects).