emacs-patch-review
Port of Thunderbird Patch Review to mu4e.
git clone git://mccd.space/emacs-patch-reviewcommit 82473af65d88e39804504b03dbf5840b8365d002
parent 0a1e75708bd92feee244070c5e3958a3b1734c9e
Author: Pi Agent <agent@pi.local>
Date: Mon, 3 Aug 2026 11:19:43 +0200
Add mail/README.org documenting the mail backend install
Covers mail-auth, mbsyncrc, mu init, msmtp, the turnstile service
(j, sv u/status/restart), the init.el use-package snippet, the first
on-passkey-touch flow, folder-name verification after the first sync,
and the Gmail All-Mail / Expunge caveats.
Diffstat:
| A | mail/README.org | | | 156 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1 file changed, 156 insertions(+), 0 deletions(-)
diff --git a/mail/README.org b/mail/README.org
@@ -0,0 +1,155 @@
+#+TITLE: Mail backend for patch-review (mbsync + mu4e + turnstile)
+
+Bundles the plumbing that [[../patch-review-mu4e.el][patch-review-mu4e]] sits on top of:
+two IMAP accounts kept in sync with =mbsync=, indexed by =mu4e=, sent via
+=msmtp=, refreshed on a timer by a per-user =turnstile= (runit) service.
+
+| Account | Server | Maildir | IMAP store |
+|------------------------------+----------+-------------------+------------|
+| marc.coquand@piva.earth | Gmail | ~/Maildir/gmail | piva |
+| marc@coquand.email | Fastmail | ~/Maildir/fastmail | fastmail |
+
+Fastmail's IMAP/SMTP login is =marcc@fastmail.fr=.
+
+Passwords are pulled from =passage= (=passage piva/gmail=,
+=passage fastmail/marcc=). Because that needs a passkey *touch* on every
+decryption, they are cached on tmpfs (once per boot) by [[file:mail-auth][mail-auth]];
+no other component ever stores a password.
+
+* Files
+
+| File | Purpose |
+|-------------------------+------------------------------------------------------------|
+| [[file:mbsyncrc]] | isync config (Channel per account, =Group all=) |
+| [[file:mail-auth]] | sh helper: =passage= → ~~/.cache/-on-tmpfs =password cache= |
+| [[file:msmtprc.example]] | one account per From, =passwordeval= reuses the cache |
+| [[file:mu4e-mail.el]] | two =mu4e-context=, bookmarks, msmtp routing on send |
+| [[file:service/run]] | turnstile runit =run=: =mail-auth && mbsync -a= loop |
+| [[file:service/conf]] | optional env overrides |
+| [[file:service/log/run]] | svlogd logger → ~/.local/share/mbsync/log |
+
+* Install
+
+All shell snippets use the repo at =~/src/emacs-patch-review=; adjust if
+you cloned elsewhere.
+
+** 1. password helper
+
+#+begin_src sh
+install -m0755 /srv/src/emacs-patch-review/mail/mail-auth ~/.local/bin/mail-auth
+#+end_src
+
+It must be on =PATH= (already for the =mccd= user) or referenced by
+=$MBSYNC_AUTH=.
+
+** 2. mbsync config
+
+#+begin_src sh
+ln -sf /srv/src/emacs-patch-review/mail/mbsyncrc ~/.mbsyncrc
+mkdir -p ~/Maildir
+mu init --maildir ~/Maildir \
+ --my-address marc.coquand@piva.earth \
+ --my-address marc@coquand.email
+#+end_src
+
+=mu init= is one-shot; the =--my-address= entries let mu mark sent mail
+as "mine" so the =U= in mu4e ("Unread") excludes your own replies.
+
+** 3. msmtp (sending)
+
+#+begin_src sh
+cp /srv/src/emacs-patch-review/mail/msmtprc.example ~/.msmtprc
+chmod 600 ~/.msmtprc
+#+end_src
+
+Your =init.el= already sets =send-mail-function= ='sendmail-send-it= and
+=sendmail-program= =/usr/bin/msmtp= in the =message= use-package. If that
+is still overridden to =ssh-sendmail= for the legacy =space-mail= flow
+(search for the =sendmail-program= override after the message block),
+remove or guard that line so msmtp wins for outgoing mail globally.
+
+** 4. turnstile runit service
+
+=turnstiled= runs a per-user =runsvdir= over =~/.config/service/= (see
+=/usr/libexec/turnstile/runit=). Each service is a directory with a
+=run= executable:
+
+#+begin_src sh
+mkdir -p ~/.config/service/mbsync/log
+ln -sf /srv/src/emacs-patch-review/mail/service/run ~/.config/service/mbsync/run
+ln -sf /srv/src/emacs-patch-review/mail/service/conf ~/.config/service/mbsync/conf
+ln -sf /srv/src/emacs-patch-review/mail/service/log/run ~/.config/service/mbsync/log/run
+chmod +x ~/.config/service/mbsync/run ~/.config/service/mbsync/log/run
+#+end_src
+
+On next login turnstiled starts =mbsync= automatically; in an already
+logged-in session:
+
+#+begin_src sh
+sv u mbsync # start
+sv s mbsync # status / one loop's tail
+sv t mbsync # restart cleanly
+# current log:
+tail -f ~/.local/share/mbsync/log/current
+#+end_src
+
+If you use lingering (=var/lib/turnstiled/linger=, see
+=turnstiled.conf(5)=) the service keeps running after logout too.
+
+** 5. mu4e in Emacs
+
+Add to =init.el= (after the existing =message= block):
+
+#+begin_src elisp
+(use-package mu4e-mail
+ :ensure nil
+ :load-path "/srv/src/emacs-patch-review/mail")
+#+end_src
+
+That =require=s =mu4e-mail=, which itself puts the repo root on
+=load-path= and wires =patch-review-mu4e-review= onto =R= in
+=mu4e-view-mode= automatically.
+
+* First run
+
+#+begin_src sh
+~/.local/bin/mail-auth # touch the passkey once
+sv t mbsync # or: mbsync -a
+mu info # check the store
+#+end_src
+
+The passkey is touched again only when
+=$XDG_RUNTIME_DIR/mbsync/{gmail,fastmail}.pass= vanish (reboot / logout
+without lingering). Set =MBSYNC_PW_TTL=<seconds>= in
+=[[file:service/conf][service/conf]] to also rotate the cache on a timer, at
+the cost of an extra passkey touch every interval.
+
+* Checking folder names
+
+Maildir path strings in [[file:mu4e-mail.el]] must match what =mbsync=
+writes on disk. After the first sync:
+
+#+begin_src sh
+find ~/Maildir -maxdepth 3 -type d -name 'cur' -printf '%h\n' \
+ | sed "s#$HOME/Maildir/#/#" | sort
+#+end_src
+
+Expect among others =/gmail/[Gmail]/Sent Mail= and =/fastmail/Sent=. If
+Gmail exposes something different (labels translated, "Sent" instead of
+"[Gmail]/Sent Mail"), edit =mu4e-sent-folder= / =mu4e-drafts-folder= /
+=mu4e-trash-folder= in the corresponding context in [[file:mu4e-mail.el]].
+
+* Caveats
+
+- =[Gmail]/All Mail= is deliberately **not** synced (it duplicates every
+ message). Refiling a Gmail message moves it to a =/gmail/Archive=
+ label rather than to All Mail, which is non-lossy on Gmail.
+- =Expunge Both= is set on both channels. Gmail keeps everything in All
+ Mail regardless, so deletes are reversible from the web; Fastmail
+ respects its Trash expunge normally.
+- =mbsync -a= runs the =Group all= (piva + fastmail). Sync one account
+ with =mbsync piva= / =mbsync fastmail=; list remote mailboxes with
+ =mbsync -l piva=.
+- The service does **not** index mu (mu4e's =mu4e-update-interval= does,
+ so no contention on mu's lock). If you want a headless index too,
+ add =mu index= to the loop in [[file:service/run][service/run]].
+\ No newline at end of file