;;; app/email/config.el -*- lexical-binding: t; -*- ;; I want to live in Emacs. Living is incomplete without email, so Emacs needs ;; to give me the ability to read, search, write and send my email. It does so ;; with `mu4e', and requires `offlineimap' and `mu' to be installed. (defvar +email-mu4e-mail-path "~/.mail" "The directory path of mu's maildir.") ;; ;; Config ;; (def-setting! :email (label letvars &optional default-p) "Registers an email address for mu4e. The LABEL is a string. LETVARS are a list of cons cells (VARIABLE . VALUE) -- you may want to modify: + `user-full-name' (this or the global `user-full-name' is required) + `user-mail-address' (required) + `smtpmail-smtp-user' (required for sending mail from Emacs) OPTIONAL: + `mu4e-sent-folder' + `mu4e-drafts-folder' + `mu4e-trash-folder' + `mu4e-refile-folder' + `mu4e-compose-signature' DEFAULT-P is a boolean. If non-nil, it marks that email account as the default/fallback account." `(after! mu4e (let ((account-vars ,letvars)) (when-let* ((address (cdr (assq 'user-mail-address account-vars)))) (cl-pushnew address mu4e-user-mail-address-list :test #'equal)) (let ((context (make-mu4e-context :name ,label :enter-func (lambda () (mu4e-message "Switched to %s" ,label)) :leave-func (lambda () (mu4e-clear-caches)) :match-func (lambda (msg) (when msg (string-prefix-p (format "/%s" ,label) (mu4e-message-field msg :maildir)))) :vars ,letvars))) (push context mu4e-contexts) ,(when default-p `(setq-default mu4e-context-current context)))))) ;; ;; Plugins ;; (def-package! mu4e :commands (mu4e mu4e-compose-new) :config (setq mu4e-maildir +email-mu4e-mail-path mu4e-attachment-dir "~/Downloads" mu4e-user-mail-address-list nil mu4e-update-interval nil mu4e-compose-format-flowed t ; visual-line-mode + auto-fill upon sending mu4e-view-show-addresses t ;; try to show images mu4e-view-show-images t mu4e-view-image-max-width 800 ;; don't save message to Sent Messages, Gmail/IMAP takes care of this mu4e-sent-messages-behavior 'delete ;; allow for updating mail using 'U' in the main view: ;; for mbsync ;; mu4e-headers-skip-duplicates t ;; mu4e-change-filenames-when-moving nil ;; mu4e-get-mail-command "mbsync -a" ;; for offlineimap mu4e-get-mail-command "offlineimap -o -q" ;; configuration for sending mail message-send-mail-function #'smtpmail-send-it smtpmail-stream-type 'starttls ;; start with the first (default) context; mu4e-context-policy 'pick-first ;; compose with the current context, or ask mu4e-compose-context-policy 'ask-if-none ;; use helm/ivy mu4e-completing-read-function (cond ((featurep! :completion ivy) #'ivy-completing-read) ((featurep! :completion helm) #'completing-read) (t #'ido-completing-read)) ;; close message after sending it message-kill-buffer-on-exit t ;; no need to ask mu4e-confirm-quit nil ;; remove 'lists' column mu4e-headers-fields '((:account . 12) (:human-date . 12) (:flags . 4) (:from . 25) (:subject)) mu4e-bookmarks `(("\\\\Inbox" "Inbox" ?i) ("\\\\Draft" "Drafts" ?d) ("flag:unread AND \\\\Inbox" "Unread messages" ?u) ("flag:flagged" "Starred messages" ?s) ("date:today..now" "Today's messages" ?t) ("date:7d..now" "Last 7 days" ?w) ("mime:image/*" "Messages with images" ?p))) ;; Add a column to display what email account the email belongs to. (add-to-list 'mu4e-header-info-custom '(:account :name "Account" :shortname "Account" :help "Which account this email belongs to" :function (lambda (msg) (let ((maildir (mu4e-message-field msg :maildir))) (format "%s" (substring maildir 1 (string-match-p "/" maildir 1))))))) ;; In my workflow, emails won't be moved at all. Only their flags/labels are ;; changed. Se we redefine the trash and refile marks not to do any moving. ;; However, the real magic happens in `+email|gmail-fix-flags'. ;; ;; Gmail will handle the rest. (setq mu4e-marks (assq-delete-all 'delete mu4e-marks)) (setq mu4e-marks (assq-delete-all 'trash mu4e-marks)) (push '(trash :char ("d" . "▼") :prompt "dtrash" :dyn-target (lambda (target msg) (mu4e-get-trash-folder msg)) :action (lambda (docid msg target) (mu4e~proc-move docid (mu4e~mark-check-target target) "+S-u-N"))) mu4e-marks) ;; Refile will be my "archive" function. (setq mu4e-marks (assq-delete-all 'refile mu4e-marks)) (push '(refile :char ("r" . "▶") :prompt "refile" :show-target (lambda (target) "archive") :action (lambda (docid msg target) (mu4e~proc-move docid (mu4e~mark-check-target target) "+S-u-N"))) mu4e-marks) ;; This hook correctly modifies gmail flags on emails when they are marked. ;; Without it, refiling (archiving), trashing, and flagging (starring) email ;; won't properly result in the corresponding gmail action, since the marks ;; are ineffectual otherwise. (defun +email|gmail-fix-flags (mark msg) (cond ((eq mark 'trash) (mu4e-action-retag-message msg "-\\Inbox,+\\Trash,-\\Draft")) ((eq mark 'refile) (mu4e-action-retag-message msg "-\\Inbox")) ((eq mark 'flag) (mu4e-action-retag-message msg "+\\Starred")) ((eq mark 'unflag) (mu4e-action-retag-message msg "-\\Starred")))) (add-hook 'mu4e-mark-execute-pre-hook #'+email|gmail-fix-flags) ;; Refresh the current view after marks are executed (defun +email*refresh (&rest _) (mu4e-headers-rerun-search)) (advice-add #'mu4e-mark-execute-all :after #'+email*refresh) (when (featurep! :feature spellcheck) (add-hook 'mu4e-compose-mode-hook #'flyspell-mode)) ;; Wrap text in messages (add-hook! 'mu4e-view-mode-hook (setq-local truncate-lines nil)) (when (fboundp 'imagemagick-register-types) (imagemagick-register-types)) (after! evil (cl-loop for str in '((mu4e-main-mode . normal) (mu4e-view-mode . normal) (mu4e-headers-mode . normal) (mu4e-compose-mode . normal) (mu4e~update-mail-mode . normal)) do (evil-set-initial-state (car str) (cdr str))) (setq mu4e-view-mode-map (make-sparse-keymap) ;; mu4e-compose-mode-map (make-sparse-keymap) mu4e-headers-mode-map (make-sparse-keymap) mu4e-main-mode-map (make-sparse-keymap)) (map! (:map (mu4e-main-mode-map mu4e-view-mode-map) :leader :n "," #'mu4e-context-switch :n "." #'mu4e-headers-search-bookmark :n ">" #'mu4e-headers-search-bookmark-edit :n "/" #'mu4e~headers-jump-to-maildir) (:map (mu4e-headers-mode-map mu4e-view-mode-map) :localleader :n "f" #'mu4e-compose-forward :n "r" #'mu4e-compose-reply :n "c" #'mu4e-compose-new :n "e" #'mu4e-compose-edit) (:map mu4e-main-mode-map :n "q" #'mu4e-quit :n "u" #'mu4e-update-index :n "U" #'mu4e-update-mail-and-index :n "J" #'mu4e~headers-jump-to-maildir :n "c" #'+email/compose :n "b" #'mu4e-headers-search-bookmark) (:map mu4e-headers-mode-map :n "q" #'mu4e~headers-quit-buffer :n "r" #'mu4e-compose-reply :n "c" #'mu4e-compose-edit :n "s" #'mu4e-headers-search-edit :n "S" #'mu4e-headers-search-narrow :n "RET" #'mu4e-headers-view-message :n "u" #'mu4e-headers-mark-for-unmark :n "U" #'mu4e-mark-unmark-all :n "v" #'evil-visual-line :nv "d" #'+email/mark :nv "=" #'+email/mark :nv "-" #'+email/mark :nv "+" #'+email/mark :nv "!" #'+email/mark :nv "?" #'+email/mark :nv "r" #'+email/mark :nv "m" #'+email/mark :n "x" #'mu4e-mark-execute-all :n "]]" #'mu4e-headers-next-unread :n "[[" #'mu4e-headers-prev-unread (:localleader :n "s" 'mu4e-headers-change-sorting :n "t" 'mu4e-headers-toggle-threading :n "r" 'mu4e-headers-toggle-include-related :n "%" #'mu4e-headers-mark-pattern :n "t" #'mu4e-headers-mark-subthread :n "T" #'mu4e-headers-mark-thread)) (:map mu4e-view-mode-map :n "q" #'mu4e~view-quit-buffer :n "r" #'mu4e-compose-reply :n "c" #'mu4e-compose-edit :n "o" #'ace-link-mu4e :n "" #'mu4e-view-headers-prev :n "" #'mu4e-view-headers-next :n "[m" #'mu4e-view-headers-prev :n "]m" #'mu4e-view-headers-next :n "[u" #'mu4e-view-headers-prev-unread :n "]u" #'mu4e-view-headers-next-unread (:localleader :n "%" #'mu4e-view-mark-pattern :n "t" #'mu4e-view-mark-subthread :n "T" #'mu4e-view-mark-thread :n "d" #'mu4e-view-mark-for-trash :n "r" #'mu4e-view-mark-for-refile :n "m" #'mu4e-view-mark-for-move)) (:map mu4e~update-mail-mode-map :n "q" #'mu4e-interrupt-update-mail)))) (def-package! mu4e-maildirs-extension :after mu4e :config (mu4e-maildirs-extension-load)) (def-package! org-mu4e :hook (mu4e-compose-mode . org-mu4e-compose-org-mode) :config (setq org-mu4e-link-query-in-headers-mode nil org-mu4e-convert-to-html t) ;; Only render to html once. If the first send fails for whatever reason, ;; org-mu4e would do so each time you try again. (add-hook! 'message-send-hook (setq-local org-mu4e-convert-to-html nil)))