diff --git a/docs/plans/panel-notifications.md b/docs/plans/panel-notifications.md new file mode 100644 index 0000000..7175b08 --- /dev/null +++ b/docs/plans/panel-notifications.md @@ -0,0 +1,106 @@ +# Plan: panel-notifications + +**Status:** candidate - the design questions are closed with the owner (2026-10-12); taking it into work still needs his word +**Date:** 2026-10-12 +**Version:** `2.3.0` MINOR (one migration; opt-out by a switch). + +--- + +## Goal + +The panel writes to its administrators when something needs them, instead of +waiting to be opened. + +## Decisions (owner, 2026-10-12) + +### Events of the first version + +Four, each of them something the panel can already tell and a person has to +act on: + +| Event | Who is told | +|---|---| +| A sending domain's DNS record stopped matching (SPF, DKIM, DMARC) | global users, and `domain` users the domain is assigned to | +| A level-2 rate limit refused mail | the same, for the domain of the limit | +| The TLS certificate is about to expire | global users | +| A message has been in the queue for more than **three days** | global users | + +Three days, not one (owner: "one day is not that much"): Postfix keeps a +message five, so two are left to act before it bounces. The threshold is a +constant, not a setting. + +Left for later: a process or milter socket down (a relay in that state often +cannot send), an inbound upstream unreachable, DMARC failures appearing +(noisy). Mail held in quarantine is not an event: quarantine is an idea +outside the roadmap. + +### Sender and path + +From `postmaster@`, submitted locally. Not from one of the +sending domains (owner): with several domains the choice is arbitrary, and a +message about the certificate has no business coming from a domain. + +The server's name usually has no DKIM key. One DNS record is enough for such +mail to pass SPF and DMARC on the name itself: `v=spf1 a -all` for the +hostname. Server › Health gains that check next to reverse DNS, and the guide +describes it. Without the record notifications are still sent. + +Notifications are the panel's own mail: they are not rows of the Outbound log +and count against no level-2 limit. + +A relay that cannot send cannot say so; none of the four events is "the relay +is down", and that is why. + +### Who chooses what + +- One switch for the instance under **Server › Settings**. +- One checkbox per user under **Account**, "send me notifications" - on when + the profile has an e-mail. No choice per event: four events, none of them + noise. Per-event choice comes when there are more and some are. +- A user without an e-mail gets nothing. + +### When a message is sent + +A background check once an hour for DNS, certificate and queue. + +| Event | Sent | "Cleared" message | +|---|---|---| +| DNS mismatch | after two checks in a row found it (it has held for an hour). A resolver that did not answer is not a mismatch | after two good checks in a row | +| Certificate | 14 days before expiry, and again 3 days before if it is still the same certificate | none | +| Level-2 limit refused mail | at the first refusal for a domain or application, then at most once a day for the same limit | none | +| Queue older than three days | when such a message appears | one, when none is left | + +Event state is kept in the database, so a restart announces nothing a second +time. + +## To settle while building + +- How the panel submits: `sendmail` as the unprivileged panel user, and what + that needs of the image. +- Whether a notification carries a link to the panel - there is no setting + for the panel's own address. +- Where the refusal of a level-2 limit is noticed: in the journal milter as + it refuses, or from the `rejected` rows of the send log on the hourly check. +- Schema: the user's checkbox, the instance switch, the event state table. + +## Screens + +Not drawn. The Notifications box was taken out of the `account` and +`settings` mockups on 2026-10-10 (owner: 2.0 ships no box for a feature that +is not built). Drawing them again - one checkbox on Account, one switch on +Server › Settings, the SPF check on Server › Health - is the first step, shown +to the owner before any code ([panel-redesign.md](panel-redesign.md) +§ Enforcement). + +## Implementation checklist (draft; counted from the day the item is agreed) + +- [ ] Mockups: Account checkbox, Settings switch, Health SPF check, help topic; accepted by the owner (`design:` commit) +- [ ] Migration and store: user flag, instance switch, event state +- [ ] Sending: local submission from `postmaster@`, message text +- [ ] Background checker: DNS, certificate, queue; the rules of *When a message is sent* +- [ ] Level-2 refusal event +- [ ] Panel: Account, Settings, Health check, help +- [ ] guide.md, architecture.md, security.md, schema-migrations.md +- [ ] `go vet`, `go test`, e2e: an event reaches a mailbox once and clears once +- [ ] Technical review +- [ ] Security review diff --git a/docs/plans/panel-redesign.md b/docs/plans/panel-redesign.md index a5ba08e..e06b711 100644 --- a/docs/plans/panel-redesign.md +++ b/docs/plans/panel-redesign.md @@ -237,7 +237,7 @@ the first step of that feature's plan — not invented during implementation. | inbound-antispam-panel (agreed, `2.1.0`) | journal = **Inbound › Log** (add client IP and engine to the mockup's columns; `quarantine` joins the decisions); lists = new **Inbound › Filter lists**, global-only because the lists are instance-wide | list layout: add form + two tables, `status_tag`, filter form in `box_head`; the log links *Allow / Deny sender* to the lists | `in-log`, `in-filter-lists` | | inbound-quarantine (an idea, off the roadmap since 2026-10-12) | new **Inbound › Quarantine**, list + detail with *Release* / *Discard* | list layout; detail like `out-message` with `facts`, `log_pane` for headers only, Release beside a `sp-danger-zone` Discard | `in-quarantine`, `in-quarantine-message` — the open questions of its plan (where mail lives, what release means, retention, RBAC) stay open; the mockup assumes SelfPost-held mail, release to upstream, 14 days, delegated like the rest of Inbound | | preflight (candidate) | new **Server › Preflight**, next to Health: Health is what runs now, Preflight is the deeper on-demand installation check with a test e-mail form | `postmark` verdict, a table of checks with `status_tag` and a *What to do* column, one form box for the test e-mail | `preflight` | -| panel-notifications (candidate) | per-user event choice on **Account**; one switch under **Server › Settings** | checkboxes in a box, `help` | **no** — drawn once, taken out of `account` and `settings` on 2026-10-10 (owner): 2.0 ships no box for a feature that is not built; redrawn as the first step of its plan | +| panel-notifications (candidate) | one checkbox per user on **Account** (no choice per event, owner 2026-10-12); one switch under **Server › Settings** | a checkbox in a box, `help` | **no** — drawn once, taken out of `account` and `settings` on 2026-10-10 (owner): 2.0 ships no box for a feature that is not built; redrawn as the first step of its plan | | password-reset (candidate) | a link on sign-in, two signed-out screens (request, set new password) | the signed-out layout | **no** | | delivery-log-storage (candidate) | the *Delivery log* box of **Outbound › Log › message**, filled from a table instead of a grep of today's `mail.log` | `log_pane`, unchanged | `out-message` | | csrf-tokens | no screen; every form gains a hidden field — forms are rendered through one helper so the token cannot be forgotten | — | n/a | diff --git a/docs/roadmap.md b/docs/roadmap.md index 48f7c0c..4f1162d 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -35,7 +35,7 @@ in `git log` and [CHANGELOG.md](../CHANGELOG.md). | template-data-typing | Typed structs for template data instead of `map[string]any` | candidate | — | — | | structured-logging | `log/slog` with levels and fields | candidate | — | — | | review-2026-08-followups | Remaining findings of the 2026-08-19 code review | candidate | — | — | -| panel-notifications | E-mail notifications about important events | candidate | — | — | +| panel-notifications | E-mail notifications about important events | candidate, design closed | — | [plans/panel-notifications.md](plans/panel-notifications.md) | | delivery-log-storage | Keep a message's `mail.log` lines with its send-log row | shipped `2.2.0` | 8/8 | [plans/delivery-log-storage.md](plans/delivery-log-storage.md) | | password-reset | Password reset by e-mail | candidate | — | — | | phone-desktop-mode | A phone browser in "desktop site" mode gets the phone navigation | candidate | — | — | @@ -338,30 +338,35 @@ checklist: [plans/delivery-log-storage.md](plans/delivery-log-storage.md). ## panel-notifications **Goal:** the panel tells its administrators when something needs them instead -of waiting to be opened: a DNS record stops matching, a domain or application -hits its rate limit and mail is refused, an inbound upstream is unreachable, -DMARC failures appear; for the global -administrator also the TLS certificate nearing expiry, a process or milter -socket down, a queue older than a day. +of waiting to be opened. First version, four events: a sending domain's DNS +record stops matching, a level-2 rate limit refuses mail, the TLS certificate +is about to expire, a message has been in the queue for more than three days. -**Boundary:** e-mail only, sent through the relay itself from -`postmaster@`. Each user picks events under **Account**; a domain -administrator hears only about assigned domains. One message when a problem -starts and one when it clears — no streams, no digests in v1. One instance -switch under **Server › Settings**. No webhooks, no chat integrations. +**Boundary:** e-mail only, from `postmaster@`, submitted locally - +not from one of the sending domains. One checkbox per user under **Account** +(no choice per event), one instance switch under **Server › Settings**; a +domain administrator hears only about assigned domains. No streams, no +digests, no webhooks, no chat integrations. Not in the first version: a +process or milter down, an inbound upstream unreachable, DMARC failures. -**Done when:** an event from the list reaches the account e-mail of every user -who chose it and may see its subject, once on start and once on clear; a user -without an e-mail gets nothing; the switch turns it all off. +**Done when:** each of the four events reaches the account e-mail of every +user who has notifications on and may see its subject, by the rules of the +plan (a "cleared" message for DNS and the queue only); a user without an +e-mail gets nothing; the switch turns it all off. **Dependencies / risks:** the account e-mail (2.0 baseline, see [plans/panel-redesign.md](plans/panel-redesign.md) § Account e-mail); a relay -that cannot send cannot report that it cannot send — server events need the -direct-to-MX path or stay panel-only; event state must survive restarts so a -reboot does not re-announce everything. Screens are drawn: `account`, -`settings`. +that cannot send cannot report that it cannot send - none of the four events +is "the relay is down"; mail from the bare hostname needs an SPF record for +it to be believed, which Server › Health will check; event state must survive +restarts so a reboot does not re-announce everything. The screens are not +drawn: the Notifications box was taken out of `account` and `settings` on +2026-10-10 and is redrawn as the first step. -**Version:** `2.x` MINOR; `candidate`. +**Version:** `2.3.0` MINOR; `candidate` - the design questions were closed +with the owner on 2026-10-12 +([plans/panel-notifications.md](plans/panel-notifications.md)); taking it into +work still needs his word. ---