Files
mesh-catalog/modules/mesh-mail/README.md
T
jochen 6c10288f0b mesh-mail declares the mail seats and claims mail-store (issue 800 part A)
Declares mail-store and mail-webclient (held once for the mesh) and mail-notifier
(held once per machine, needing node-notifier) per ADR 0378 as amended by ADR 0379.
Claims mail-store and serves its four verbs through the same handlers as the
existing mail_* tools. Moves the event shape (EventKey, Changes) into its own
file, mailevent.go, so mesh-mail-notifier (issue 800 part B) can carry a
byte-identical copy. Adds checks/mailstore, refusing any manifest but mesh-mail's
own that claims mail-store (ADR 0378 §3), wired into merge-check.sh.
2026-10-15 17:16:53 +02:00

5.6 KiB

mesh-mail — the operator's mail accounts, watched and said on the bus

One module holds the operator's mail accounts, watches them, and says each change of a message on the bus for the modules the operator grants it to (novox/hq ADR 0359, research 049 document 02). One instance, on the control-node. Reading is the first stage: accounts of the kind imap, read-only — nothing here flags, moves, deletes or drafts. Sending is mail_send alone (novox/hq ADR 0361 §2): it sends nothing itself, and reaches an account's outgoing server only once the operator approves it on a channel that proves who answered.

Its seats (novox/hq ADR 0378 as amended by ADR 0379, issue 800 part A)

It declares three mail seats and claims the one it holds:

  • mail-store (held once for the mesh) — serves accounts, search, read and send; mesh-mail claims it, through the same handlers as its own mail_* tools. The catalogue's own check (checks/mailstore) refuses any other manifest that claims it.
  • mail-webclient (held once for the mesh) — serves open and status; unheld until issue 580.
  • mail-notifier (held once per machine, needing node-notifier there) — serves status; held by mesh-mail-notifier (issue 800 part B), a module of its own built from this one's source, which tells the operator of each new inbox mail through node-notifier.send.

What it does

  • Watches. For each account it keeps one connection: IDLE on the inbox, ended and begun again every two minutes (which proves the server still answers), and a look at every other folder every five minutes. Folders are opened read-only and bodies are peeked, so watching never marks a message seen.
  • Indexes. One row per message and folder in its own database (postgres-database): metadata, flags, the text for search, and the attachments' references. Each attachment is kept once, by its SHA-256, in its bucket (s3-bucket, from minio).
  • Says each change. A message added, its flags changed, or removed is said as the event mail.<account>.message.<added|changed|removed>: account, folder, uid, message id, date, sender, recipients, subject, size, flags and attachment references; never the body, never an attachment's bytes. The events go to the module's own stream MAIL, kept seven days, never to the shared EVENTS stream (novox/hq ADR 0363); a module hears an account's part only when its consumes names mesh-mail and the setting grants names it for that account. The first look at a folder indexes what is there and says nothing: the bus hears what changed since the watch began. The keeper writes each event in the same step as the change it says; the bundle says it through the runtime and removes it once the bus took it.

Its settings, both trusted

accounts — per account name (a lower-case token, which names the account on the bus and its secret):

{"mesh": {"kind": "imap", "address": "<the address>",
          "incoming": {"host": "<the IMAP server>", "port": 993, "security": "tls"},
          "outgoing": {"host": "<the SMTP server>", "port": 465, "security": "tls"},
          "folders": ["optional: the folders watched besides the inbox; every folder when left out"]}}

outgoing is the server mail_send sends through: host, port, and security tls (465, implicit) or starttls (587). A second imap account on another server is one more entry and one more password; no code changes. user signs in under another name than the address.

grants — per account, who may read its mail, and who may send from it: a module's name (on every machine), <machine>.<module>, or person.<name>. Nobody by default. It restricts the stream MAIL on the bus (a person's entry grants nothing there) and the tools mail_search, mail_read and mail_send — the same check for all three.

The password of an account is the own secret mail-credential-<account>, given at the desk; the mesh never makes one.

Its tools

Tool Answers
mail_accounts each account's kind, state and why, when its server last answered, its folders; the address only to a caller granted it
mail_search messages of the accounts the caller is granted, by every word of a query, newest first, no body
mail_read one message's metadata and text, or one attachment's bytes, to a caller granted its account
mail_health healthy; unhealthy naming the account and why (connection down, or no answer for five minutes); waits naming mail-credential-<account> while its password is not given
mail_send raises an ask on the operator channel (the account, the recipients, the subject, the first lines) for a caller granted the account; answers at once with the ask's id. Sends nothing itself
mail_send_status where a send stands, by its ask id: waiting, sent, declined, ended without a choice, or not proven

The caller is the runtime's word, never an argument. mail_send sends through the account's outgoing server only once the operator's Approve comes back on a channel that proves who answered, and once: a warrant delivered twice never sends twice (novox/hq ADR 0361 §2).

The mail-store seat's four verbs, mail-store.accounts, mail-store.search, mail-store.read and mail-store.send, answer through the same handlers as mail_accounts, mail_search, mail_read and mail_send above — the same grants, the same refusals, the same ask-then-Approve for a send.

Its processes

prepare brings the index's schema to this version's (numbered migrations, and schema.sql for a fresh database). keep watches the accounts and is judged by its unit. watch judges every minute and is judged by mail_health.