emacs-patch-review
Port of Thunderbird Patch Review to mu4e.
git clone git://mccd.space/emacs-patch-reviewcommit 0a1e75708bd92feee244070c5e3958a3b1734c9e
parent eb64d4a527f6604f8a9a591f7752766243c9453c
Author: Pi Agent <agent@pi.local>
Date: Mon, 3 Aug 2026 11:19:43 +0200
Add mu4e-mail: two-context mu4e setup with mbsync/msmtp plumbing
Two mu4e-contexts match on maildir prefix (/gmail, /fastmail) and set
user-mail-address, sent/drafts/trash/refile folders, and a per-account
message-sendmail-extra-arguments ("-a" "piva"/"fastmail") so msmtp
routes outbound mail correctly. mu4e-get-mail-command is "true" and
mu4e-update-interval is 300s: the turnstile service fetches, mu4e only
re-indexes. mu4e-change-filenames-when-moving is set for mbsync. When
loadable, the file auto-wires patch-review-mu4e-review onto R in
mu4e-view by adding the repo root to load-path.
Diffstat:
| A | mail/mu4e-mail.el | | | 141 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1 file changed, 141 insertions(+), 0 deletions(-)
diff --git a/mail/mu4e-mail.el b/mail/mu4e-mail.el
@@ -0,0 +1,140 @@
+;;; mu4e-mail.el --- mu4e setup for two IMAP accounts -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2026 Marc Coquand
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;;; Commentary:
+
+;; Loads alongside `patch-review-mu4e' and gives the patch-review mail
+;; workflow its plumbing: two mu4e contexts (Gmail / Fastmail), an mbsync
+;; config and a turnstile runit service that keeps ~/Maildir in sync.
+;;
+;; Loading this file requires the system-wide mu4e shipped at
+;; /usr/share/emacs/site-lisp/mu4e (already on `load-path' on Void). It
+;; silently wires up `patch-review-mu4e-review' on `R' if the patch-review
+;; package is loadable from the directory above this file.
+;;
+;; See README.org for the system-side install (mbsyncrc, mail-auth,
+;; ~/.config/service/mbsync, mu init).
+
+;;; Code:
+
+(require 'mu4e)
+(require 'message)
+(require 'mu4e-message nil t)
+
+
+;;; --- patch-review integration -------------------------------------------------
+
+;; Put the repo root on `load-path' so `patch-review-mu4e' resolves even if
+;; the user only added this `mail/' subdirectory.
+(let* ((here (file-name-directory (or load-file-name buffer-file-name "/----")))
+ (root (file-name-directory (directory-file-name here))))
+ (when (file-exists-p (expand-file-name "patch-review-mu4e.el" root))
+ (add-to-list 'load-path root)))
+
+(when (require 'patch-review-mu4e nil t)
+ (with-eval-after-load 'mu4e-view
+ (define-key mu4e-view-mode-map (kbd "R") #'patch-review-mu4e-review)))
+
+
+;;; --- outbound mail ------------------------------------------------------------
+
+;; Sending goes through msmtp, one account per context (see msmtprc.example).
+;; These mirror what init.el's `message' use-package already sets; restating
+;; them here keeps mu4e self-contained if loaded on its own.
+(setq send-mail-function 'sendmail-send-it
+ sendmail-program "/usr/bin/msmtp"
+ message-sendmail-envelope-from 'header
+ mail-envelope-from 'header
+ mail-user-agent 'message-user-agent
+ message-user-agent 'message-user-agent
+ message-kill-buffer-on-exit t)
+
+
+;;; --- mu4e globals -------------------------------------------------------------
+
+(setq mu4e-change-filenames-when-moving t ; recommended with mbsync
+ ;; Mail is fetched by the turnstile service; mu4e only re-indexes.
+ mu4e-get-mail-command "true"
+ mu4e-update-interval 300 ; re-index every 5 min when open
+ mu4e-index-cleanup nil
+ mu4e-completing-read-function #'completing-read-default
+ mu4e-attachment-dir (expand-file-name "~/Downloads")
+ message-kill-buffer-on-exit t)
+
+
+;;; --- contexts -----------------------------------------------------------------
+
+(defun mu4e-mail--maildir-prefix? (msg prefix)
+ "Return non-nil if MSG's :maildir starts with PREFIX."
+ (when-let* ((dir (and (functionp 'mu4e-message-field)
+ (mu4e-message-field msg :maildir))))
+ (string-prefix-p prefix dir)))
+
+(setq mu4e-contexts
+ `(
+ ,(make-mu4e-context
+ :name "piva (Gmail)"
+ :match-func
+ (lambda (msg) (mu4e-mail--maildir-prefix? msg "/gmail"))
+ :vars '((user-mail-address . "marc.coquand@piva.earth")
+ (user-full-name . "Marc Coquand")
+ (mu4e-drafts-folder . "/gmail/[Gmail]/Drafts")
+ (mu4e-sent-folder . "/gmail/[Gmail]/Sent Mail")
+ (mu4e-trash-folder . "/gmail/[Gmail]/Trash")
+ ;; A top-level Gmail "label" we create on first refile; on
+ ;; Gmail that removes the INBOX label without data loss.
+ (mu4e-refile-folder . "/gmail/Archive")
+ (message-sendmail-extra-arguments . ("-a" "piva"))))
+
+ ,(make-mu4e-context
+ :name "fastmail"
+ :match-func
+ (lambda (msg) (mu4e-mail--maildir-prefix? msg "/fastmail"))
+ :vars '((user-mail-address . "marc@coquand.email")
+ (user-full-name . "Marc Coquand")
+ (mu4e-drafts-folder . "/fastmail/Drafts")
+ (mu4e-sent-folder . "/fastmail/Sent")
+ (mu4e-trash-folder . "/fastmail/Trash")
+ (mu4e-refile-folder . "/fastmail/Archive")
+ (message-sendmail-extra-arguments . ("-a" "fastmail"))))))
+
+;; Pick the right context automatically when replying; for a brand-new
+;; message that has no parent ("no current context yet"), mu4e prompts once.
+;; `user-mail-address'/`user-full-name' are themselves context vars below,
+;; so plain `message-mail' outside mu4e tracks the current context too.
+(setq mu4e-context-policy 'ask-if-none)
+
+
+;;; --- bookmarks & shortcuts ---------------------------------------------------
+
+(setq mu4e-bookmarks
+ '((:name "Unread"
+ :query "flag:unread AND NOT flag:trashed"
+ :key ?u :favorite t)
+ (:name "Today"
+ :query "date:today..now"
+ :key ?t)
+ (:name "Last 7 days"
+ :query "date:7d..now"
+ :key ?w)
+ (:name "Sent (gmail)"
+ :query "maildir:\"/gmail/[Gmail]/Sent Mail\""
+ :key ?G)
+ (:name "Sent (fastmail)"
+ :query "maildir:/fastmail/Sent"
+ :key ?S)))
+
+(setq mu4e-maildir-shortcuts
+ '((:maildir "/gmail/INBOX" :key ?g :name "piva inbox")
+ (:maildir "/fastmail/INBOX" :key ?f :name "fastmail inbox")
+ (:maildir "/gmail/Archive" :key ?a :name "piva archive")
+ (:maildir "/fastmail/Archive" :key ?A :name "fastmail archive")))
+
+(provide 'mu4e-mail)
+;;; mu4e-mail.el ends here
+\ No newline at end of file