diff --git a/03-DESIGN/00-as-is/18-the-mail-modules.md b/03-DESIGN/00-as-is/18-the-mail-modules.md index 250f0291..906fd567 100644 --- a/03-DESIGN/00-as-is/18-the-mail-modules.md +++ b/03-DESIGN/00-as-is/18-the-mail-modules.md @@ -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`). diff --git a/03-DESIGN/01-to-be/59-the-mail-web-client.md b/03-DESIGN/01-to-be/59-the-mail-web-client.md index d4d9da86..46e5dd12 100644 --- a/03-DESIGN/01-to-be/59-the-mail-web-client.md +++ b/03-DESIGN/01-to-be/59-the-mail-web-client.md @@ -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,