feat(emacs): add mu4e

This commit is contained in:
Ray Andrew 2025-12-26 02:32:37 -06:00
parent eb73545d64
commit 440c93fbf2
Signed by: rayandrew
SSH key fingerprint: SHA256:XYrYrxF0Z3A72n8P/p6mqPRNQZT22F88XcLsG+kX4xw
3 changed files with 223 additions and 6 deletions

View file

@ -583,8 +583,8 @@ frame if FRAME is nil, and to 1 if AMT is nil."
;; ========================= ;; =========================
;; Agenda variables ;; Agenda variables
(setq org-directory "~/Documents/org/") ; Non-absolute paths for agenda and (setq org-directory "~/NPersonal/") ; Non-absolute paths for agenda and
; capture templates will look here. ; capture templates will look here.
(setq org-agenda-files '("inbox.org" "work.org")) (setq org-agenda-files '("inbox.org" "work.org"))
@ -614,8 +614,8 @@ frame if FRAME is nil, and to 1 if AMT is nil."
;;; Phase 3 variables ;;; Phase 3 variables
;; Org-roam variables ;; Org-roam variables
(setq org-roam-directory "~/Documents/org-roam/") (setq org-roam-directory "~/NPersonal/roam/")
(setq org-roam-index-file "~/Documents/org-roam/index.org") (setq org-roam-index-file "~/NPersonal/roam/index.org")
;;; Optional variables ;;; Optional variables
@ -653,7 +653,13 @@ frame if FRAME is nil, and to 1 if AMT is nil."
("wm" "Work meeting" entry (file+headline "work.org" "Meetings") ("wm" "Work meeting" entry (file+headline "work.org" "Meetings")
"** TODO %?\n%U\n%i\n%a") "** TODO %?\n%U\n%i\n%a")
("wr" "Work report" entry (file+headline "work.org" "Reports") ("wr" "Work report" entry (file+headline "work.org" "Reports")
"** TODO %?\n%U\n%i\n%a"))) "** TODO %?\n%U\n%i\n%a")
;; Email capture
("e" "Email")
("et" "Email TODO" entry (file "inbox.org")
"* TODO %:fromname: %:subject\n%U\n%a\n\n%?")
("ef" "Email Follow-up" entry (file "inbox.org")
"* TODO Follow up: %:subject\nDEADLINE: %(org-insert-time-stamp (org-read-date nil t \"+2d\"))\n%U\n%a\n\n%?")))
(setq org-agenda-custom-commands (setq org-agenda-custom-commands
'(("n" "Agenda and All Todos" '(("n" "Agenda and All Todos"
((agenda) ((agenda)
@ -661,6 +667,120 @@ frame if FRAME is nil, and to 1 if AMT is nil."
("w" "Work" agenda "" ("w" "Work" agenda ""
((org-agenda-files '("work.org"))))))) ((org-agenda-files '("work.org")))))))
;; =========================
;; Email (mu4e)
;; =========================
(use-package mu4e
:ensure (:host github :repo "djcb/mu"
:files ("mu4e/*.el")
:pre-build (with-temp-file "mu4e/mu4e-config.el"
(insert (format "(defconst mu4e-mu-version %S)\n"
(string-trim (shell-command-to-string "mu --version | head -1 | awk '{print $2}'")))
"(provide 'mu4e-config)\n")))
:defer t
:commands (mu4e mu4e-compose-new)
:bind ("C-c e" . mu4e)
:config
;; Set mu binary path (for Nix)
(setq mu4e-mu-binary (or (executable-find "mu")
"/etc/profiles/per-user/rayandrew/bin/mu"))
;; General settings
(setq mu4e-maildir "~/mail")
(setq mu4e-get-mail-command "~/dotfiles/bin/path-shim mbsync -a")
(setq mu4e-update-interval 180) ;; Update every 3 minutes
(setq mu4e-change-filenames-when-moving t) ;; Required for mbsync
;; Sending mail via msmtp
(setq sendmail-program "msmtp")
(setq send-mail-function 'sendmail-send-it)
(setq message-sendmail-f-is-evil t)
(setq message-sendmail-extra-arguments '("--read-envelope-from"))
(setq message-send-mail-function 'message-send-mail-with-sendmail)
;; Don't keep message buffers around
(setq message-kill-buffer-on-exit t)
;; Display settings
(setq mu4e-headers-date-format "%Y-%m-%d")
(setq mu4e-headers-time-format "%H:%M")
(setq mu4e-view-show-addresses t)
(setq mu4e-view-show-images t)
(setq mu4e-compose-dont-reply-to-self t)
;; Prefer plain text over HTML
(setq mm-discouraged-alternatives '("text/html" "text/richtext"))
;; GPG signing (not by default)
(setq mml-secure-openpgp-sign-with-sender t)
(setq mm-sign-option nil) ;; don't sign by default
;; Keybinding to sign message (in compose buffer)
(defun mu4e-compose-sign-message ()
"Sign the current message with GPG."
(interactive)
(mml-secure-message-sign-pgpmime))
(add-hook 'mu4e-compose-mode-hook
(lambda ()
(local-set-key (kbd "C-c C-s") 'mu4e-compose-sign-message)))
;; Keybindings: r for reply, R for reply-all
(define-key mu4e-headers-mode-map (kbd "r") 'mu4e-compose-reply)
(define-key mu4e-headers-mode-map (kbd "R") 'mu4e-compose-wide-reply)
(define-key mu4e-view-mode-map (kbd "r") 'mu4e-compose-reply)
(define-key mu4e-view-mode-map (kbd "R") 'mu4e-compose-wide-reply)
;; Keybinding: e for refile (archive)
(define-key mu4e-headers-mode-map (kbd "e") 'mu4e-headers-mark-for-refile)
(define-key mu4e-view-mode-map (kbd "e") 'mu4e-view-mark-for-refile)
;; t to mark (u to unmark is default)
(define-key mu4e-headers-mode-map (kbd "t") 'mu4e-headers-mark-for-something)
;; Use completing-read (works with vertico)
(setq mu4e-completing-read-function 'completing-read)
;; Contexts for multiple accounts
(setq mu4e-contexts
`(,(make-mu4e-context
:name "uchicago"
:match-func (lambda (msg)
(when msg
(string-prefix-p "/uchicago" (mu4e-message-field msg :maildir))))
:vars '((user-mail-address . "rayandrew@uchicago.edu")
(user-full-name . "Ray Andrew")
(mu4e-drafts-folder . "/uchicago/Drafts")
(mu4e-sent-folder . "/uchicago/Sent")
(mu4e-trash-folder . "/uchicago/Trash")
(mu4e-refile-folder . "/uchicago/Archive")
(message-sendmail-extra-arguments . ("--read-envelope-from" "-a" "uchicago"))))
,(make-mu4e-context
:name "personal"
:match-func (lambda (msg)
(when msg
(string-prefix-p "/personal" (mu4e-message-field msg :maildir))))
:vars '((user-mail-address . "raydreww@gmail.com")
(user-full-name . "Ray Andrew")
(mu4e-drafts-folder . "/personal/[Gmail]/Drafts")
(mu4e-sent-folder . "/personal/[Gmail]/Sent Mail")
(mu4e-trash-folder . "/personal/[Gmail]/Trash")
(mu4e-refile-folder . "/personal/[Gmail]/All Mail")
(message-sendmail-extra-arguments . ("--read-envelope-from" "-a" "personal"))))))
;; Set default context
(setq mu4e-context-policy 'pick-first)
(setq mu4e-compose-context-policy 'ask-if-none)
;; Bookmarks
(setq mu4e-bookmarks
'((:name "Unread messages" :query "flag:unread AND NOT flag:trashed" :key ?u)
(:name "Today's messages" :query "date:today..now" :key ?t)
(:name "Last 7 days" :query "date:7d..now" :key ?w)
(:name "UChicago Inbox" :query "maildir:/uchicago/Inbox" :key ?i)
(:name "Personal Inbox" :query "maildir:/personal/Inbox" :key ?p))))
(use-package wakatime-mode (use-package wakatime-mode
:config :config
(global-wakatime-mode)) (global-wakatime-mode))

97
docs/mu4e.md Normal file
View file

@ -0,0 +1,97 @@
# mu4e Configuration
## Launch
| Key | Action |
|-----|--------|
| `C-c e` | Open mu4e |
## Main View
| Key | Action |
|-----|--------|
| `U` | Update mail (run mbsync) |
| `j` | Jump to maildir |
| `s` | Search |
| `b` | Bookmarks |
| `C` | Compose new message |
| `;` | Switch context (account) |
| `q` | Quit mu4e |
## Headers View (Message List)
| Key | Action |
|-----|--------|
| `n` / `p` | Next / previous message |
| `RET` | View message |
| `d` | Mark for trash |
| `D` | Mark for delete |
| `t` | Mark for action (prompts) |
| `u` | Unmark |
| `m` | Move to folder |
| `e` | Refile (archive) |
| `+` / `-` | Mark / unmark flag |
| `!` | Mark as read |
| `?` | Mark as unread |
| `r` | Reply |
| `R` | Reply all |
| `F` | Forward |
| `x` | Execute marked actions |
| `u` | Unmark |
| `U` | Unmark all |
| `*` | Mark for something |
| `#` | Mark for deferred trash |
| `%` | Mark by pattern |
| `T` | Mark thread |
| `g` | Rerun search |
| `q` | Quit to main view |
## Message View
| Key | Action |
|-----|--------|
| `n` / `p` | Next / previous message |
| `SPC` / `DEL` | Scroll down / up |
| `r` | Reply |
| `R` | Reply all |
| `F` | Forward |
| `o` | Open attachment |
| `e` | Save attachment |
| `a` | View action (open in browser, etc.) |
| `#` | Toggle citation folding |
| `.` | Raw view |
| `h` | Toggle headers |
| `q` | Return to headers |
## Compose
| Key | Action |
|-----|--------|
| `C-c C-c` | Send message |
| `C-c C-k` | Kill (discard) message |
| `C-c C-d` | Save as draft |
| `C-c C-a` | Attach file |
| `C-c C-s` | GPG sign message (custom) |
| `C-c C-e` | GPG encrypt message |
## Bookmarks (from main view, press `b`)
| Key | Action |
|-----|--------|
| `u` | Unread messages |
| `t` | Today's messages |
| `w` | Last 7 days |
| `i` | UChicago Inbox |
| `p` | Personal Inbox |
## Search Tips
- `from:john` - messages from john
- `to:jane` - messages to jane
- `subject:meeting` - subject contains meeting
- `flag:unread` - unread messages
- `flag:attach` - messages with attachments
- `date:today..now` - today's messages
- `date:7d..now` - last 7 days
- `maildir:/uchicago/Inbox` - specific maildir
- `AND`, `OR`, `NOT` - boolean operators

View file

@ -46,7 +46,7 @@
muCmd = lib.optionalString config.custom.email.mu '' muCmd = lib.optionalString config.custom.email.mu ''
# Initialize mu if not already done # Initialize mu if not already done
if [ ! -d "${home-dir}/.cache/mu" ] && [ ! -d "${home-dir}/.mu" ]; then if [ ! -d "${home-dir}/.cache/mu" ] && [ ! -d "${home-dir}/.mu" ]; then
${pkgs.mu}/bin/mu init --maildir=${home-dir}/mail ${pkgs.mu}/bin/mu init --maildir=${home-dir}/mail --my-address=rayandrew@uchicago.edu --my-address=raydreww@gmail.com
fi fi
${pkgs.mu}/bin/mu index ${pkgs.mu}/bin/mu index
''; '';