;;;; .emacs.d/mu4e-init.el ;;;; mu4e configuration ;;;; ;;;; last updated: 2024/08/26 ;;;; Author: HEO SeonMeyong ;;; Require packages upfront (add-to-list 'load-path "/usr/local/share/emacs/site-lisp/mu/mu4e/") (require 'mu4e) ; mu4e ;; (require 'mu4e-speedbar) ; Speedbar (require 'smtpmail) ; Compose and send ;;; Debugging ;; (setq mu4e-mu-debug t) ; for mu debbuging ;; (setq mu4e-debug t) ; for mu4e debugging ;;; for mu4e general settings ;; external Binaries. (setq mu4e-get-mail-command (concat (executable-find "mbsync") " -a")) ; this command is called to sync imap servers (setq mu4e-mu-binary (executable-find "mu")) ; mu binary filename. Installed by HomeBrew. (setq sendmail-program (executable-find "msmtp")) ; send Email program ;; Directories. (setq mu4e-maildir "~/Maildir") ; this is the directory we created before. (setq mu4e-attachment-dir "~/Desktop") ; save attachment to desktop ;; Manipulate mails (setq mu4e-change-filenames-when-moving t) ; rename files when moving - needed for mbsync (setq mu4e-update-interval (* 10 60)) ; how often to call it in seconds ;; overall behavivior (setq mu4e-confirm-quit nil) ; don't have to confirm when quitting (setq mu4e-modeline-show-global t) ; mu4e shares information on the modeline (add-hook 'mu4e-headers-search-hook (lambda (query) (setq-local global-mode-string '(:eval (concat (mu4e-context-label) " " (propertize (mu4e~quote-for-modeline mu4e~headers-last-query) 'face 'mu4e-modeline-face 'help-echo (format "%s" mu4e~headers-last-query))))))) (setq mu4e-modeline-max-width 100) ; Change mu4e modeline to 100 chars (setq mode-name "mu4e-headers") ; Add mode name to modeline (add-hook 'mu4e-headers-search-hook (lambda (query) (setq mode-name "mu4e"))) (setq mu4e-split-view "vertical") ; Split-View. Using with display-buffer-alist (add-to-list 'display-buffer-alist ; for Body-view window `(,(regexp-quote mu4e-view-buffer-name) display-buffer-in-side-window (side . right) (window-width . 0.5))) (setq mu4e-hide-index-messages t) ; hide annoying "mu4e Retrieving mail..." msg in mini buffer (setq mu4e-context-policy 'pick-first) ; start with the first (default) context. (pick-first, ask, ask-if-none, nil) ;; headers-view (setq mu4e-headers-include-related nil) ; by default do not show related emails (setq mu4e-headers-results-limit 5000) ; Limit of message view search results. default is 500 (setq mu4e-headers-show-threads t) ; by default do not show threads (setq mu4e-headers-date-format "%Y-%m-%dT%H:%M:%S") ; Header's time format. (setq mu4e-headers-fields ; Show headers list -- field and width(nil means unlimited) '( (:date . 20) ; alternatively, use :human-date (:flags . 6) ; Mail flags (:from . 20) ; From field (:to . 20) ; To field (:subject . nil))) ; alternatively, use :thread-subject ;; Body-view ;; prefer text messages. (with-eval-after-load "mm-decode" (add-to-list 'mm-discouraged-alternatives "text/html") (add-to-list 'mm-discouraged-alternatives "text/richtext")) (setq mu4e-view-date-format "%Y-%m-%dT%H:%M:%S") (setq mu4e-view-fields '(:from ; From field :to ; To field :cc ; Cc field :subject ; Subject field :flags ; Mail flags :date ; Date field(maybe send date and time) :maildir ; maildir information :mailing-list ; Mailing list field :tags ; Tags :attachments ; Attachment information :signature ; Message signature :decryption ; Decryption :message-id ; Message-ID :path ; Mail stored directory :user-agent ; User agent information )) ;; Draft and Compose (setq mu4e-compose-context-policy 'ask-if-none) ; ask for context if no context matches.(pick-first, ask, ask-if-none, nil) (setq mu4e-compose-signature-auto-include nil) ; Signature auto include when composed (defvaralias 'mu4e-compose-signature 'message-signature) ; Use signature variables. (setq message-citation-line-format "%N @ %Y-%m-%d %H:%M:%S :\n") ; customize the reply-quote-string: (setq message-citation-line-function 'message-insert-formatted-citation-line) ; M-x find-function RET message-citation-line-format for docs ;; Sending mails (setq mail-user-agent 'mu4e-user-agent) ; Use mu4e for e-mail in emacs (setq send-mail-function 'sendmail-send-it message-send-mail-function 'sendmail-send-it) ; Reconfigure Email send function (setq message-kill-buffer-on-exit t) ; don't keep message compose buffers around after sending: (setq message-sendmail-envelope-from 'header) ; select the right sender email from the context. ;; Additional supports (setq mu4e-eldoc-support t) ; get info about the current header in the echo-area. (add-hook 'mu4e-compose-mode-hook 'company-mode) ; mu4e address completion (add-hook 'dired-mode-hook 'turn-on-gnus-dired-mode) ; attach files to mu4e messages using dired. See info "13.9 Dired" ;;; Personal environment/Variables ;;; Contexts (setq mu4e-contexts (list ;; sample@example.com (make-mu4e-context :name "sample_example" :match-func (lambda (msg) (when msg (string-prefix-p "/sample_example" (mu4e-message-field msg :maildir)))) :vars '((user-mail-address . "sample@example.com" ) (user-full-name . "dovecot sample") (mu4e-drafts-folder . "/sample_example/Drafts") (mu4e-sent-folder . "/sample_example/Sent") (mu4e-trash-folder . "/sample_example/Trash") )) ;; sample@icloud.com (make-mu4e-context :name "sample_icloud" :match-func (lambda (msg) (when msg (string-prefix-p "/sample_icloud" (mu4e-message-field msg :maildir)))) :vars '((user-mail-address . "sample@icloud.com") (user-full-name . "iCloud example") (mu4e-drafts-folder . "/sample_icloud/Drafts") (mu4e-sent-folder . "/sample_icloud/Sent") (mu4e-trash-folder . "/sample_icloud/Trash") )) ;; sample@gmail.com (make-mu4e-context :name "sample_gmail" :enter-func (lambda () (mu4e-message "Enter sample@gmail.com context")) :leave-func (lambda () (mu4e-message "Leave sample@gmail.com context")) :match-func (lambda (msg) (when msg (string-prefix-p "/sample_gmail" (mu4e-message-field msg :maildir)))) :vars '((user-mail-address . "sample@gmail.com") (user-full-name . "Gmail sample") (mu4e-drafts-folder . "/sample_gmail/Drafts") (mu4e-sent-folder . "/sample_gmail/Sent") (mu4e-trash-folder . "/sample_gmail/Trash") )) )) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Bookmarks (setq mu4e-bookmarks "") (setq mu4e-bookmarks '( ( :name "Unread messages" :query "flag:unread AND NOT flag:trashed" :favorite t :key ?u) ( :name "Inbox - sample@example" :query "maildir:/sample_example/Inbox" :favorite t :key ?1) ( :name "Inbox - sample.icloud" :query "maildir:/sample_icloud/Inbox" :favorite t :key ?2) ( :name "Inbox - sample.yahoo" :query "maildir:/sample_yahoo/Inbox" :favorite t :key ?3) ( :name "Inbox - sample.gmail" :query "maildir:/sample_gmail/Inbox" :favorite t :key ?4) ( :name "SPAM/UCE" :query "maildir:/sample_example/Spam OR maildir:/sample_icloud/Spam OR maildir:/sample_gmail/Spam OR maildir:/sample_yahoo/Spam" :favorite t :key ?s) ( :name "Trash" :query "maildir:/sample_example/Trash OR maildir:/sample_icloud/Trash OR maildir:/sample_gmail/Trash OR maildir:/sample_yahoo/Trash" :favorite t :key ?t) ( :name "Today's messages" :query "date:today..now" :favorite t :key ?T) )) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Maildir Shortcuts (setq mu4e-maildir-shortcuts ;; Set IMAP Sub-directories... '( (:maildir "/sample_icloud/Finamce" :name "Fin" :key ?f) (:maildir "/sample_icloud/Game" :name "Game" :key ?g) (:maildir "/sample_gmail/MyDiv" :name "MyDiv" :key ?d) (:maildir "/sample_gmail/Logs" :name "Logs" :key ?l) (:maildir "/sample_yahoo/Agent" :name "Agent" :key ?a) (:maildir "/sample_example/Family" :name "Fam" :key ?f) )) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Refile rules (makunbound 'my-mu4e-refile-rules) (defvar my-mu4e-refile-rules '( ;; === Financial ("/sample_icloud/Finamce" "from" "foobank\\.co\\.jp$") ("/sample_icloud/Finamce" "from" "barbank\\.co\\.jp$") ("/sample_icloud/Finamce" "from" "bazcard\\.co\\.jp$") ("/sample_icloud/Finamce" "from" "hogepoint\\.jp$") ;; === Game ("/sample_icloud/Game" "from" "ninnin\\.\\(com\\|net\\|co\\.jp\\)$") ("/sample_icloud/Game" "from" "FourToKnight@mail\\.epicgames\\.com$") ;; === MyDiv ("/sample_gmail/MyDiv" "from" "sample@gmail\\.com$") ;; === Log ("/sample_gmail/Logs" "to" "log@example\\.com$") ("/sample_gmail/Logs" "to" "log@gmail\\.com$") ("/sample_gmail/Logs" "from" "^root@") ("/sample_gmail/Logs" "subj" "^Cron") ("/sample_gmail/Logs" "subj" "run\\.output$") ;; === Agent ("/sample_yahoo/Agent" "to" "sample+agent@\\(yahoo\\.co\\.jp\\|example\\.com\\)") ;; === Family ("/sample_example/Family" "from" "wife@foo\\.jp$") ("/sample_example/Family" "from" "son@bar\\.ac\\.jp$") ) "List of (refile-folder field regex) triples for refiling. Field can be 'to', 'cc', 'bcc', 'rcpt', 'from', 'subject', 'any', 'msgid' or 'list' for mailing lists.") ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Personal functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Select SMTP account from from header at composed email (defun my-mu4e-set-msmtp-account () ; chose from account before sending (if (message-mail-p) (save-excursion (let* ((from (save-restriction (message-narrow-to-headers) (message-fetch-field "from"))) (account (cond ((string-match "sample@example.com" from) "sample_example") ((string-match "sample@icloud.com" from) "sample_icloud") ((string-match "sample@yahoo.co.jp" from) "sample_yahoo") ((string-match "sample@gmail.com" from) "sample_gmail") ))) (setq message-sendmail-extra-arguments (list '"-a" account))) ))) (add-hook 'message-send-mail-hook 'my-mu4e-set-msmtp-account) ;;; Add Cc and Bcc field when compose Email. (add-hook 'mu4e-compose-mode-hook ; mu4e cc & bcc (defun my-add-cc-and-bcc () "My Function to automatically add Cc & Bcc: headers. This is in the mu4e compose mode." (save-excursion (message-add-header (concat "Cc: " user-mail-address "\n"))) (save-excursion (message-add-header "Bcc:\n")))) ;;; Require confirmation before sending mail without subject. (defun confirm-empty-subject () "Require confirmation before sending without subject." (let ((sub (message-field-value "Subject"))) (or (and sub (not (string-match "\\`[ \t]*\\'" sub))) (yes-or-no-p "Really send without Subject? ") (keyboard-quit)))) (add-hook 'message-send-hook #'confirm-empty-subject) ;;; Header viewでmail Search時にMaxmumを拡張して全部を検索する (defvar my-mu4e-page 1 "Current page in mu4e search.") (defun my-mu4e-reset-page (&rest _r) "Reset ‘my-mu4e-page’ to 1." (setq my-mu4e-page 1)) ;; Need to reset the "standard searches" when invoking an interactive search (add-hook 'mu4e-search-bookmark-hook #'my-mu4e-reset-page) (advice-add 'mu4e-search-maildir :before #'my-mu4e-reset-page) (defun my-mu4e-next-messages-for-query () "Fetch the next number of messages for current mu4e query. Move to last message in current view so that newly fetched messages are visible." (interactive) (when (and (mu4e-current-buffer-type-p 'headers) (not mu4e-search-full)) (when-let ((query (mu4e-last-query))) (cl-incf my-mu4e-page) (let ((mu4e-search-results-limit (* my-mu4e-page -1)) (last-msg (save-excursion (goto-char (point-max)) (forward-line -1) (plist-get (mu4e-message-at-point) :message-id)))) (mu4e-search query nil nil t last-msg))))) (keymap-set mu4e-headers-mode-map "N" #'my-mu4e-next-messages-for-query) ;;; Refile (makunbound 'my-mu4e-refile-message) (defun my-mu4e-refile-message (msg) "Determine the refile folder for mu4e messages based on specified fields in MSG." (cl-loop for (folder field regex) in my-mu4e-refile-rules for addresses = (cond ((string= field "any") (append (mu4e-message-field msg :to) (mu4e-message-field msg :cc) (mu4e-message-field msg :bcc) (mu4e-message-field msg :from))) ((string= field "rcpt") (append (mu4e-message-field msg :to) (mu4e-message-field msg :cc) (mu4e-message-field msg :bcc))) ((string= field "subj") (list (mu4e-message-field msg :subject))) ((string= field "msgid") (list (mu4e-message-field msg :msgid))) ((string= field "list") (list (mu4e-message-field msg :list))) (t (mu4e-message-field msg (intern (concat ":" field))))) when (seq-some (lambda (addr) (when addr (string-match-p regex (downcase (if (listp addr) (or (plist-get addr :email) "") addr))))) addresses) return folder ; refile先を相対pathで返す finally return nil)) ; 条件にマッチしない場合はnilを返す ;; mu4e-refile-message関数を利用してrefileする (setq mu4e-refile-folder "") (setq mu4e-refile-folder (lambda (msg) (or (my-mu4e-refile-message msg) (mu4e-message-field msg :maildir)))) ; リファイル先が決定できない場合は現在のフォルダを返す (makunbound 'my-mu4e-auto-refile-process) (defun my-mu4e-auto-refile-process (msg) "Process a single message for auto-refiling." (let ((target-folder (funcall mu4e-refile-folder msg))) (when (and target-folder (not (string= target-folder (mu4e-message-field msg :maildir)))) (mu4e-mark-set 'refile target-folder)))) (makunbound 'my-mu4e-auto-refile) (defun my-mu4e-auto-refile () "Automatically mark messages for refiling in the current folder based on mu4e-refile-folder rules." (interactive) (let* ((current-folder (mu4e-message-field (mu4e-message-at-point) :maildir)) (query (concat "maildir:" current-folder " " "AND (flag:new OR flag:unread) " "AND NOT flag:trashed"))) (mu4e-headers-search query) (add-hook 'mu4e-headers-found-hook 'my-mu4e-auto-refile-hook))) (makunbound 'my-mu4e-auto-refile-hook) (defun my-mu4e-auto-refile-hook () "Hook to process messages after headers search." (remove-hook 'mu4e-headers-found-hook 'my-mu4e-auto-refile-hook) (goto-char (point-min)) (while (not (eobp)) (let ((msg (mu4e-message-at-point))) (my-mu4e-auto-refile-process msg)) (forward-line)) (mu4e-headers-next nil)) ; Move to the next unread message after marking (makunbound 'my-mu4e-headers-auto-refile) (defun my-mu4e-headers-auto-refile () "Run auto-refile marking in headers view." (interactive) (my-mu4e-auto-refile)) (keymap-set mu4e-headers-mode-map "e" #'my-mu4e-headers-auto-refile) ; Refile key bind ;; Local Variables: ;; coding: utf-8 ;; comment-column: 72 ;; version-control: t ;; kept-old-versions: 2 ;; kept-new-versions: 2 ;; End: