Answer the review: the mail modules are separate modules, the notifier has no process, nothing future in the as-is; 1433 depends on none

This commit is contained in:
jochen
2026-10-20 19:11:06 +02:00
parent e925a843ce
commit dcc6f185d4
2 changed files with 28 additions and 26 deletions
+23 -24
View File
@@ -15,8 +15,8 @@ decisions:
# The mail modules, as they run
**One module, `mesh-mail`, holds every mail account and is the mesh's one `mail-store`. A second module built
from its source, `mesh-mail-web`, is the mesh's one mail web client. A third, `mesh-mail-notifier`, shows a
**One module, `mesh-mail`, holds every mail account and is the mesh's one `mail-store`. A second module,
`mesh-mail-web`, is the mesh's one mail web client. A third, `mesh-mail-notifier`, shows a
desktop notice of new mail on the machines it is assigned to. A caller may read an account's mail, or ask to
send from it, only when `mesh-mail`'s trusted setting `grants` names it for that account — one list per
account, giving both at once, to the same callers. A send is never performed by a tool: it is queued for a
@@ -26,7 +26,7 @@ process of its own, which sends only once the operator approves it on Telegram.*
```
mesh-mail (mail-store, once for the mesh — the control-node)
accounts search read send send-status ── MAIL stream (headers only, 7 days) ──▶ a granted caller
own database (index) · own object-store bucket (attachments) · two key-value buckets (account state, newest mails)
own database (index) · own object-store bucket (attachments) · three key-value buckets (sends, account state, newest mails)
process keep (IDLE + index) · process watch (mail_health) · process send (mesh-mail-send account; holds the passwords)
▲ grants: one list per account, read AND send alike
│
@@ -44,11 +44,14 @@ mesh-mail (mail-store, once for the mesh — the control-node)
2026-10-20). ADR 0378 §4 decided to move it to the home server as a step of its own, on the operator's
Approve (issue 803); as built, that move has not happened: the live mesh still runs it, and `mesh-mail-web`
beside it, on the control-node.
- **`mesh-mail-web`** is built from `mesh-mail`'s own source, as `mesh-mail-notifier` is, and claims
`mail-webclient`, held once for the mesh. It is assigned to the same machine as `mesh-mail` and runs as its
- **`mesh-mail-web`** is a module of its own, with its own Go module and source under `modules/mesh-mail-web`;
it claims `mail-webclient`, held once for the mesh. It is assigned to the same machine as `mesh-mail` and runs as its
own account, `mesh-mail-web`. Its manifest declares no database and no store of its own: nothing beyond its
route, its sign-in binding and its own settings on disk.
- **`mesh-mail-notifier`** is built from the same source and claims `mail-notifier`, held once per machine; a
- **`mesh-mail-notifier`** is a module of its own as well (`modules/mesh-mail-notifier`), and keeps a
byte-identical copy of `mesh-mail`'s `mailevent.go`, held to it by its `copies_test.go`, because each module
builds alone. ADR 0378 §9 and ADR 0440 §1 say both are "built from `mesh-mail`'s source"; as built, they are
separate modules that share no code but that copied file. It claims `mail-notifier`, held once per machine; a
module claiming it needs `node-notifier` held on the same machine (ADR 0378 §7). It is assigned today to two
machines, the laptop and the workstation (live read of each machine's modules, 2026-10-20); the control-node
and the home server hold no instance of it.
@@ -66,6 +69,8 @@ mesh-mail (mail-store, once for the mesh — the control-node)
the other folders; for each new, changed or removed message it publishes an event on its own stream `MAIL` —
account, folder, message id, date, from, to, subject, size, flags and attachment references, never a body or
an attachment's bytes (ADR 0359 §2). The stream keeps 7 days (`module.json`).
- **A send's own record** lives in its state bucket `sends`, which only the `send` process and the tools write
(`module.json`; ADR 0436 §8).
- **The index and the attachment store are its own.** A row per message and folder lives in its own Postgres
database; attachments live in an object-store bucket, each kept once by its SHA-256 digest (ADR 0359 §3, ADR
0439 §5-§6).
@@ -83,7 +88,7 @@ mesh-mail (mail-store, once for the mesh — the control-node)
- **One check, every door.** The code that decides whether a caller may read an account's mail (`mail_search`,
`mail_read`, the account's share of `MAIL`) and the code that decides whether it may ask to send from it
(`mail_send`) is the same function, reading the one list (`modules/mesh-mail/cmd/mesh-mail/config.go`'s
`Grants.Allows`, called from `tools.go` and `send.go`, confirmed on `origin/main` at `a67f0196`). There is no
`Grants.Allows`, called from `tools.go` for every tool, `mail_send` included, confirmed on `origin/main` at `a67f0196`). There is no
second list and no narrower grant for sending alone: a caller named for an account may do both, and a caller
left out of it may do neither.
- **Who is granted, today, for every account alike** (`mesh-controller.settings` for `mesh-mail`, read live on
@@ -142,9 +147,10 @@ mesh-mail (mail-store, once for the mesh — the control-node)
- **One notice per new inbox mail, at most once.** For each account it is granted, on hearing `mesh-mail`'s
`added` event, it shows the sender (name and address), the subject and the account's name through the
machine's `node-notifier`, under its own account (`runs-as`), never the tool runner's.
- **Quiet rules.** It shows nothing for a mail outside the inbox, for a mail from the account's own address, for
a mail already flagged read when it arrives, and for anything during an account's first sync or a resync
(`notifier.go`, `mailevent.go`).
- **Quiet rules.** It shows nothing for a mail outside the inbox, for a mail from the account's own address, and
for a mail already flagged read when it arrives (`notifier.go`, `mailevent.go`). During an account's first sync
or a resync `mesh-mail` publishes no `added` events, so nothing reaches the notifier then (`notifier.go`'s
opening note, citing `mesh-mail`'s `imap.go`).
- **Folding.** More than three qualifying mails of one account within a trailing 60-second window become one
notice, shown once the window has passed since the fold's own first mail, naming how many and the latest
sender; a message already shown — tracked by its message id, or by its folder, UIDVALIDITY and UID where it
@@ -161,17 +167,10 @@ mesh-mail (mail-store, once for the mesh — the control-node)
for five minutes, healthy otherwise; its `send` process is a plain unit (ADR 0359 §6; `health.go`).
- **`mesh-mail-web`**'s own tool `mail_web_health` reads the one state its HTTP endpoint keeps: still setting up
sign-in, listening, or ended with its error — unhealthy whenever it is not listening (`main.go`).
- **`mesh-mail-notifier`**'s own tool `mail_notifier_health` is unhealthy once an account's notice has failed to
show for more than ten minutes. As built it answers this only when asked directly: its one process (`notify`)
is still declared with a plain unit health alone, because the catalogue's gate refuses a module judged only by
its own tool with no check of another kind beside it, and the module has only the one process — a gap its own
source names; a watching process of its own is tracked by issue 841, not yet decided (its `README.md` and
`health.go`). Design 59's milestone 2 retires the module (issue 1418).
## What design 59 changes
- **The one `grants` list splits in two.** Reading an account's mail and asking to send from it, today one
grant, become two the operator sets apart (design 59 §5; ADR 0460).
- **The notifier's pop-up retires.** `mesh-mail-notifier`'s desktop notice for every new inbox mail is replaced
by one mail signal per batch of mail reading, and it is unassigned from the laptop and the workstation once
that proves itself live (design 59 §7; ADR 0461).
- **`mesh-mail-notifier`** has no process of its own: its manifest declares only its account and its state
directory, and its bundle, loaded by the machine's tool runner, runs the watcher inside the runner's own
invocation (commit 12d78520, issue 859). Its tool `mail_notifier_health` is unhealthy once an account's notice
has failed to show for more than ten minutes, and answers only when asked; no check of the node-engine judges
the module by it. A watching process of its own is tracked by issue 841, not yet decided (its `README.md`).
The comment in its `health.go` that still names a process `notify` predates that commit. It keeps one state
bucket, `shown`, for 30 days, to show no mail twice (`module.json`).
+5 -2
View File
@@ -293,8 +293,11 @@ order (each depends on the one before unless said):
1429 (mail signals; after 1427), 1430 (the mail page; after 1428 and 1429), 1431 (the integration test; after
1430 and 1227), 1418 (`mesh-mail-notifier` unassigned; after 1431 and test 13 live).
- Milestone 3: 806 (Betterbird removed; after 1418).
- Milestone 4: 1419 (the thread's reading-only caller; a decision first), 1433 (`mail_search`'s folder case and
`-any` switches), 1432 (the mail conversation; after 1431, 1419 and design 58's milestone 3, issue 880). Milestone 2 is one release of `mesh-catalog`: the grant split, the new seat, the new
- Milestone 4: 1433 (`mail_search`'s folder case and `-any` switches; depends on none), 1419 (the thread's
reading-only caller; a decision first, after design 58's milestone 3, issue 880), 1432 (the mail conversation;
after 1431, 1419 and issue 880).
Milestone 2 is one release of `mesh-catalog`: the grant split, the new seat, the new
module and the page change ship together, and `mesh-mail-reader` is granted reading only after that delivery, on the
operator's Approve; `mesh-mail-notifier` is unassigned only after test 13 passes live. Milestone 3 is issue 806.
ADR 0379 §2's bar for removing Betterbird is milestone 1's; keeping Betterbird until mail reading (milestone 2) works,