fs-mail: protocol and platform notes
==================================

Mail protocol references checked on 2026-10-22 and 2026-10-24; filesystem-mail
precedents and synchronization/editor references checked on 2026-10-24.
These are implementation rationale and
operational limits, not a claim of testing against a live account.

Filesystem mail has established precedents
------------------------------------------

The nmh project's own mh-folders manual specifies a filesystem directory for
each folder and a separate, numerically named file for each message. It also
defines context and sequence metadata. The project describes nmh as a set of
small mail commands that can be combined with ordinary shell commands.

Sources: nmh project; mh-folders(5), distributed in Debian's nmh package
https://www.nongnu.org/nmh/
https://manpages.debian.org/trixie/nmh/mh-folders.5mh.en.html

Maildir likewise stores individual messages as files. Courier's official manual
documents tmp, new, and cur directories and explicit filename flags for states
such as Seen and Replied. Applications that understand those conventions update
the flags; ordinary file contents and arbitrary editor actions do not encode all
mail operations by themselves.

Source: Courier Mail Server, maildir manual
https://www.courier-mta.org/maildir.html

fs-mail uses a deliberately different human-readable format: decoded text,
editable To/Cc/Bcc/Attachments/Subject headers, and a blank line before the body. The
remote IMAP worker and SMTP sender supply the protocol operations. The following
are design implications, not claims of historical novelty:

- Opening a text file is not a reliable request to mark a remote message read.
  A previewer or indexer may also open it; no IMAP flag command is implied.
- Renaming a received file need not change its identity, because the client can
  remember mailbox/UIDVALIDITY/UID separately from its human-readable filename.
- Copying a received file is not an automatic reply. A reply requires choosing
  recipients and, for threading, suitable In-Reply-To/References headers.
- An outgoing .txt file placed in outbox is an explicit submission request.
  Prepare it outside the live queue, or use a non-.txt staging name until ready.

ChromeOS Linux environment
-------------------------

fs-mail runs inside the ChromeOS Linux environment. The Chromium lifecycle
documentation says that logging out shuts down running VMs/containers; logging
in alone does not restart them. Opening Terminal, a Linux application, or Linux
Files starts the relevant environment. Thus a Linux scheduler can run fs-mail
without an application window while that environment is running, but does not
itself guarantee startup at ChromeOS login. The same documentation discusses
loginctl enable-linger for user services. Keep the local mail tree under Linux
Files for normal Linux filesystem behavior.

Source: Chromium, Running Custom Containers Under ChromeOS, Lifecycles,
I'm running <insert distro here>, and Can I access files when the container
isn't running?
https://www.chromium.org/chromium-os/developer-library/guides/containers/containers-and-vms/

The systemd timer manual explains that a calendar timer catches up after
suspension, with one activation when several scheduled intervals elapsed.
Persistent=true also catches up after inactivity, but only for OnCalendar
timers. These options cannot make a stopped ChromeOS Linux VM start itself.
The last sentence is an inference from the scheduler and VM lifecycles.

Source: systemd.timer, OnCalendar and Persistent
https://www.freedesktop.org/software/systemd/man/latest/systemd.timer.html

SMTP submission and uncertainty
-------------------------------

SMTP acceptance is a server's responsibility to attempt delivery, not a receipt
from the destination person. Python SMTP.sendmail can return successfully when
only some recipients were accepted: its result maps rejected recipients to
their errors. An implementation must inspect that result or use the lower-level
MAIL, RCPT, and DATA commands to control this boundary explicitly.

Source: Python standard library, smtplib, SMTP.sendmail
https://docs.python.org/3/library/smtplib.html

The SMTP specification calls out the ambiguity after DATA: a server can accept
a message but its final reply can be lost. An automatic retry can then create a
duplicate. A durable local record plus a stable Message-ID reduces accidental
retries but cannot force a remote SMTP server to provide exactly-once delivery.
fs-mail should hold an uncertain transmission for explicit review rather than
silently resubmit it. A lost connection before DATA is a different case because
the message body has not been submitted.

Source: RFC 5321, sections 4.2.5, 4.5.3.2.6, and 6.1
https://www.rfc-editor.org/rfc/rfc5321.html

IMAP identity and non-destructive downloads
-------------------------------------------

IMAP sequence numbers can change. Cache identities must include mailbox,
UIDVALIDITY, and UID. A changed UIDVALIDITY starts a different identifier
generation; an old UID alone cannot identify the same message. EXAMINE opens a
mailbox read-only, and BODY.PEEK avoids implicitly setting the Seen flag.
Using both supports a download-only client without read-status side effects.
Version 2 uses these operations for the download phase; its reconciliation
phase selects a writable mailbox only when a remote mutation is needed.

Source: RFC 9051, sections 2.3.1.1, 6.3.3, and 6.4.5
https://www.rfc-editor.org/rfc/rfc9051.html

Three-way synchronization and precise operations
------------------------------------------------

The implementation compares a durable baseline, current files and fetched IMAP
metadata. Local-ID is a client identity independent of path; the remote identity
is still mailbox/UIDVALIDITY/UID. It writes a journal before mutation and advances
the baseline from the recorded snapshot after verifying the outcome. Changes
made during network work remain later working-tree changes. This is analogous
to version-control reconciliation, not a claim of one atomic Git transaction
spanning a filesystem and an IMAP server.

CONDSTORE provides a modification sequence and conditional STORE. A stale
UNCHANGEDSINCE can return a tagged OK with a MODIFIED response code, so checking
OK alone is insufficient. The program refuses flag updates without a usable
conditional update rather than overwriting a concurrent client's changes.

Source: RFC 7162, sections 3.1, 3.1.3 and 3.1.4
https://www.rfc-editor.org/rfc/rfc7162.html

UID EXPUNGE applies only to specified UIDs which carry Deleted. This avoids
expunging unrelated messages marked by another client. The implementation has
no global EXPUNGE or CLOSE fallback, and verifies the mailbox's UID generation
before using any cached UID. Permanent deletion still has a concurrency window
after marking Deleted; ordinary IMAP does not offer a transaction around the
whole compare/STORE/EXPUNGE sequence.

Source: RFC 4315, section 2.1
https://www.rfc-editor.org/rfc/rfc4315.html

MOVE maps a source UID to a new destination UID. COPYUID may be delivered as an
untagged OK or as a tagged response, and some permitted server circumstances
omit it. A tagged NO can still represent partial work. The program journals one
message at a time, requires a unique mapping and verifies both sides. A lost or
ambiguous result is held, not blindly retried. Explicit remote preference can
abandon the local binding while retaining the text and refresh server state.

Sources: RFC 6851, section 3.3; RFC 4315, section 3
https://www.rfc-editor.org/rfc/rfc6851.html
https://www.rfc-editor.org/rfc/rfc4315.html

Editor selection and platform boundaries
----------------------------------------

ChromeOS's Linux feature provides a Debian environment in which command-line
tools and editors can be installed. OpenBSD utilities also use EDITOR and
VISUAL, with vi as a conventional fallback. fs-mail explicitly chooses EDITOR
first, then VISUAL, then installed vi/nano. This precedence is fs-mail's policy;
it is not a claim that every system utility uses the same ordering. It passes
an argv vector without shell evaluation and does not assume either variable is
already set. The actual selected editor is discovered at invocation time.

Sources: Google, Set up Linux on your Chromebook; OpenBSD crontab(1)
https://support.google.com/chromebook/answer/9145439?hl=en
https://man.openbsd.org/crontab.1

Outgoing correctness test plan
------------------------------

1. Only ordinary, non-hidden .txt files are eligible. Require a nonempty To
   header first and a nonempty Subject, validate the remaining header block, and reject symlinks,
   special files, malformed addresses, duplicate headers, and header injection.
   Absolute attachment paths must identify regular non-symlink files.
2. Construct the actual SMTP envelope from To, Cc, and Bcc. Preserve To and Cc
   headers, omit Bcc from transmitted bytes, and deduplicate envelope addresses.
3. Simulate one rejected RCPT among multiple recipients. Verify that DATA never
   occurs and the whole draft remains queued; no recipient received its body.
4. Fail login, TLS negotiation, MAIL, every RCPT, DATA initiation, and the final
   DATA response independently. Verify each queue disposition and retry rule.
5. Simulate a timeout after DATA and a process crash between remote acceptance
   and local cleanup. Neither may cause an automatic repeat on the next run.
6. Fail local archive creation or source cleanup after successful submission.
   The accepted record must prevent resending while cleanup is retried.
7. Modify, rename, or delete the source during a send. Never delete a
   newer replacement file or erase unrelated concurrent edits. Explain a held
   item whose contents no longer match its durable send record.
8. Run overlapping invocations. A single process must own the queue operation;
   no message should be submitted concurrently by two fs-mail processes.
9. Verify normal completion archives readable sent mail plus original bytes,
   removes the submitted file from outbox, and does not create an index file.
10. Confirm status, dry-run, and legacy migration make no network connection.
    Preserve originals during migration; do not silently submit legacy bodies
    while a nonempty legacy recipient index remains.
11. Exercise UTF-8, BOM-marked UTF-16/32, supported legacy text, and invalid
    encodings. Keep Unicode in the body and filename paths without replacement
    decoding that silently discards bytes. Test quoted comma attachment paths.

Tests should use temporary directories and scripted fake servers or transport
objects. No actual recipients are needed. A live account smoke test is still
needed on the user's provider to verify its authentication configuration.
