← The specification

Notifications & alerting

Rendered from spec at76d46f3 (2026-10-25) · its source · Edit this page

Status: Accepted · Audience: Both · Area: B — Running it


Purpose

Tell the operator when something needs their attention — and tell household members when the thing they asked for has arrived.

Those two are almost opposite requirements, and conflating them is what ruins notification systems. The operator wants silence to mean healthy; a channel carrying forty “download complete” messages a day gets muted within a week, and takes “your VPN is leaking” down with it.

Behaviour

Division of labour: lemonfiber alerts, services notify

lemonfiber does not reimplement what the stack already does well. Seerr already notifies a requester when their request is approved and when it becomes available; the *arrs have their own connection systems.

Owner Events
lemonfiber Problems, and cross-service conditions no single service can see
Seerr (configured by lemonfiber) Household request approved, denied, now available
lemonfiber, to the operator’s phone only A new request waiting on the operator, and an invitation answered

The two in the last row are the operator’s own business rather than the requester’s: somebody in the house asked for something the operator decides on, or answered the operator’s invitation. They go to the operator’s phone and to nobody else; the requester and the invitee are still told by the service that owns their side (ADR-0047).

lemonfiber’s unique contribution is the conditions nothing else observes: a VPN leak, hardlink degradation, an item downloaded but never imported, a disk that will fill before the queue drains.

Appetite is chosen during setup

The wizard asks once, offering three presets rather than a checklist:

Preset Notifies on
Problems only Failures and risks. Silence means healthy.
Problems + completions The above, plus successful downloads and imports
Everything The above, plus advisories such as available updates

Every individual event remains configurable afterwards through reconfiguration. The preset sets the starting point; it is not a ceiling.

Event catalogue

Severity Event In preset
Critical VPN tunnel down or egress mismatch detected all
Critical Disk full, or projected to fill within 24h all
Critical Data root became unavailable all
Warning Queue item stuck beyond threshold all
Warning Provider quota exhausted or subscription lapsed all
Warning Service crash-looping all
Warning Imports degraded from hardlink to copy all
Warning Credential validation now failing all
Warning Backup failed all
Info Download completed completions, everything
Info Import succeeded completions, everything
Advisory Stack or lemonfiber update available everything
Advisory Backup succeeded everything

Delivery channels

Channel Notes
In-app Dashboard and health summary. Always on, no configuration, no dependency. The irreducible baseline.
Desktop Native OS notification when lemonfiber runs on the operator’s own machine. Useless headless.
ntfy Push to phone. Apache-2.0 and self-hostable, so it fits the open-source commitment. A single HTTP request.
Companion The phone app, raising each alert locally from its own connection, filtered by the kinds the operator chose for that stack; woken through UnifiedPush on Android where a distributor is installed, and best-effort while closed otherwise (ADR-0047).
Apprise Bridge to 100+ services (Discord, Telegram, email, Gotify, Pushover). One integration covers everything else.

In-app is unconditional — an alert must never exist only on a channel that might be misconfigured.

Alerts are stateful, not repeated

An ongoing condition notifies once on entry and once on resolution. It does not re-fire every poll. Repetition is how alerting becomes noise, and noise is how alerts get ignored.

Every alert carries its remedy

Consistent with G4: an alert states what happened, what it means, and what to do. “VPN check failed” is a notification; “VPN tunnel dropped — torrent traffic is halted by the killswitch, no leak occurred; restart Gluetun to resume” is useful.

On the companion

Division of labour: lemonfiber alerts, services notify extends to the app as a third party that must not join in. N10-R11 forbids the companion raising alerts of its own (N10); what is configured here is the core’s, and N4-R11 is where that is settled. A phone is the device most able to interrupt someone, which is precisely why it is not given reasons of its own to.

Appetite is chosen during setup is read on this surface rather than asked again, and N10-R8 requires a preset shown with what it means and with the exceptions the operator has made. A preset name alone is not the configuration — it is the part of it that fits on one line — and an operator working out why something did or did not reach them needs the exceptions, which are where the answer usually is.

A rehearsed alert is labelled as a rehearsal on the terms N6-R1 sets, and N10-R9 additionally forbids presenting it as an alert that was sent. On a device where a test and the real thing arrive identically, that is not a subtle distinction.

Alerts that could not be read are told apart from there being none (N10-R12).

States

Per alert condition:

State Meaning
clear Condition not present
firing Present and notified
acknowledged Operator has seen it; suppressed until it clears and recurs
resolved Cleared; a resolution notice sent if the onset was notified
suppressed Muted by configuration or an active quiet period

Edge cases

Situation Behaviour
Condition flaps rapidly Debounce. Require the condition to hold for a minimum period before notifying.
Many alerts fire at once Coalesce into a digest rather than sending twenty messages. A disk-full event cascades into many downstream failures.
Delivery channel unreachable Retry with backoff; never lose the alert — in-app always has it. Report channel failure as its own condition.
Notification would contain a credential Redact. Same rules as the support bundle.
Alert fires while the stack is intentionally stopped Suppress operational alerts when the operator stopped things deliberately.
Quiet hours configured Hold non-critical alerts; critical ones always deliver.
Household member has no notification target Seerr handles this; lemonfiber does not chase it.
Same condition on multiple services Group into one alert naming all affected services.
Alert resolves before being read Still show it in history — a transient VPN drop matters even after recovery.
Operator disables all channels Permitted. In-app remains, and lemonfiber states that external delivery is off.

Acceptance criteria

ID Requirement
B5-R1 lemonfiber MUST NOT duplicate notifications that Seerr or the *arrs already send to their own users; a new request and an answered invitation MUST reach the operator alone, as the request and member notification kinds (ARCH-R279), and MUST NOT be sent to a household member.
B5-R2 Setup MUST ask notification appetite once, offering presets rather than an event checklist.
B5-R3 Every individual event MUST remain configurable after setup.
B5-R4 In-app delivery MUST be unconditional and MUST NOT require configuration.
B5-R5 An ongoing condition MUST notify once on onset and once on resolution, never repeatedly.
B5-R6 Conditions MUST be debounced so flapping does not generate repeated alerts.
B5-R7 Simultaneous alerts MUST be coalesced into a digest.
B5-R8 Every alert MUST state what happened, what it means, and what to do.
B5-R9 Alerts MUST NOT contain credentials, subject to the same redaction as the support bundle.
B5-R10 Channel delivery failure MUST itself be surfaced, and MUST NOT cause alert loss.
B5-R11 Critical alerts MUST bypass quiet-hours suppression.
B5-R12 Deliberate operator-initiated stops MUST suppress the resulting operational alerts.
B5-R13 Alert history MUST retain conditions that resolved before being read.
B5-R14 The same condition across several services MUST produce one grouped alert.