Custom Emacs functions No. 7 - Mu4e

EDIT - 2024-11-09: See the Edits section further down.

Intro

In the name of bringing the kitchen sink inside of Emacs, I manage my mails here as well. Mu4e is my choice of a mail client.

As a quite opinionated fellow when it comes to my tools, I tend to change stuff a lot. This includes extending functions and commands that are delivered with Mu4e by default. Or even creating new ones.

Barring changes in Mu4e or related updates, these functions should remain relevant. However some where created a good while ago. Which in turn might result in “obsolete” code or easier solutions.

Commands and Functions

timu-mu4e-in-new-tab

I always, I mean ALWAYS open Mu4e in a new tab. This one does it for me with a custom keybinding.

(defun timu-mu4e-in-new-tab ()
  "Open `mu4e' in a new tab with `tab-bar-new-tab'."
  (interactive)
  (progn
    (tab-bar-new-tab)
    (mu4e)))

timu-mu4e-quit

Since I always open Mu4e in a new tab, this function allows me to close the “mail-tab” upon quitting Mu4e.

(defun timu-mu4e-quit ()
  "Quit `mu4e' and close the tab with `tab-bar-close-tab'."
  (interactive)
  (progn
    (mu4e-quit)
    (tab-bar-close-tab)))

timu-mu4e-execute-no-confirm

Executing marks – moving, deleting, archiving mails and more – in Mu4e (mostly in headers view) happens many many times a day. Having to confirm all of those action would result in me throwing my Mac out of the window after a while. The following function helps execute marks without confirmation.

(defun timu-mu4e-execute-no-confirm ()
  "Execute all without confirmation.
Use the argument NO-COMFIRM in the command `mu4e-mark-execute-all'."
  (interactive)
  (mu4e-mark-execute-all 'no-confirm))

timu-mu4e-jump-to-maildir

To simplify mailbox navigation, I use completing-read with the command below, bound to J..

(defun timu-mu4e-jump-to-maildir ()
  "Use `completing-read' to jump to a maildir.
Credit: https://emacs.stackexchange.com/a/47580/30874
Credit: https://arupajhana.wordpress.com/2014/09/26/mu4e-with-helm."
  (interactive)
  (let ((maildir (completing-read "Maildir: " (mu4e-get-maildirs))))
    (mu4e-headers-search (format "maildir:\"%s\"" maildir))))

timu-mu4e-attach-file

This one helps add attachments to a compose buffer with completion in the minibuffer (read-file-name).

(defun timu-mu4e-attach-file ()
  "Attach a file to an email.
Use the built-in function `mml-attach-file'."
  (interactive)
  (let ((default-directory "~/"))
    (let ((file (read-file-name "Select a file to attach: ")))
      (mml-attach-file (expand-file-name file)))))

timu-mu4e-view-save-attachments

The default command for saving attachments, mu4e-view-save-attachments automatically chooses a – in the variable mu4e-attachment-dir – predefined directory. This is fine and dandy, but I mostly want to select the directory my self – on a case-by-case basis.

(defun timu-mu4e-view-save-attachments ()
  "Save all attachements in a selected directory.
This is `mu4e-view-save-attachments' with prefix Argument."
  (interactive)
  (let ((current-prefix-arg '(4))
        (embark-confirm-act-all nil))
    (call-interactively #'mu4e-view-save-attachments)))

timu-mu4e-get-mail

The variable mu4e-get-mail-command defines which CLI command to use to fetch emails with Mu4e. In my case – using the isync program (mbsync) – I want sometimes to only fetch mails per account or all accounts.

(defun timu-mu4e-get-mail ()
  "Select the Account before syncing.
This makes the syncing of mails more flexible."
  (interactive)
  (let ((mu4e-get-mail-command
         (concat
          "/opt/homebrew/bin/mbsync "
          (completing-read
           "Which Account: "
           '("icloud" "aimebertrand" "moclub" "--all")))))
    (mu4e-update-mail-and-index t)))

Commands and functions to deal with signatures

The following two commands are extensively covered in the previous post “Signature above the cited text in mu4e”.

(defun timu-mu4e-message-insert-signature (&optional force)
  "Insert a signature at the end of the buffer.

Original command is `message-insert-signature'.
See https://macowners.club/posts/signature-above-cited-text-mu4e/ for reasons.

See the documentation for the `message-signature' variable for
more information.

If FORCE is 0 (or when called interactively), the global values
of the signature variables will be consulted if the local ones
are null."
  (interactive (list 0) message-mode)
  (let ((timu-message-signature timu-message-signature)
        (message-signature-file message-signature-file))
    ;; If called interactively and there's no signature to insert,
    ;; consult the global values to see whether there's anything they
    ;; have to say for themselves.  This can happen when using
    ;; `gnus-posting-styles', for instance.
    (when (and (null timu-message-signature)
               (null message-signature-file)
               (eq force 0))
      (setq timu-message-signature (default-value 'timu-message-signature)
            message-signature-file (default-value 'message-signature-file)))
    (let* ((signature
            (cond
             ((and (null timu-message-signature)
                   (eq force 0))
              (save-excursion
                (goto-char (point-max))
                (not (re-search-backward message-signature-separator nil t))))
             ((and (null timu-message-signature)
                   force)
              t)
             ((functionp timu-message-signature)
              (funcall timu-message-signature))
             ((listp timu-message-signature)
              (eval timu-message-signature t))
             (t timu-message-signature)))
           signature-file)
      (setq signature
            (cond ((stringp signature)
                   signature)
                  ((and (eq t signature) message-signature-file)
                   (setq signature-file
                         (if (and message-signature-directory
                                  ;; don't actually use the signature directory
                                  ;; if message-signature-file contains a path.
                                  (not (file-name-directory
                                        message-signature-file)))
                             (expand-file-name message-signature-file
                                               message-signature-directory)
                           message-signature-file))
                   (file-exists-p signature-file))))
      (when signature
        (goto-char (point-max))
        ;; Insert the signature.
        (unless (bolp)
          (newline))
        (when message-signature-insert-empty-line
          (newline))
        (insert "...... ")
        (newline)
        (if (eq signature t)
            (insert-file-contents signature-file)
          (insert signature))
        (goto-char (point-max))
        (or (bolp) (newline))))))
(defun timu-mu4e-message-insert-signature-at-point (pmode)
  "Function to insert signature at right point according to PMODE.
Uses `timu-mu4e-message-insert-signature'.
This is a modified version of `message-insert-signature'."
  (when pmode (message-goto-body))
  (interactive)
  (require 'message)
  (message-goto-body)
  (newline)
  (message-goto-body)
  (save-restriction
    (narrow-to-region (point) (point))
    (timu-mu4e-message-insert-signature))
  (message-goto-body))

Automatically switching the context

Whenever switching to specific mailbox, I want to be in the right mu4e-context. This is in short a setting set. In my case to mostly identify the correct account.

Based on my current research, there is no built-in solution to automatically switch contexts. I use the following function as a mu4e-headers-found-hook to achieve this.

(defun timu-mu4e-switch-context ()
  "Switch context of the current maildir.
Uses `mu4e--search-last-query' and regex to get the context."
  (let ((new-context
         (timu-get-mu4e-context)))
    (if new-context
        (mu4e-context-switch t new-context)
      (mu4e-context-switch t "icloud"))))

… Which in turn uses the following function.

(defun timu-get-mu4e-context ()
  "Extract context from `mu4e--search-last-query'."
  (if (string-match "/\\(.+?\\)/.*" mu4e--search-last-query)
      (match-string 1 mu4e--search-last-query) ""))

You can find more details in the older post “AutoSwitch Mu4e context depending on mailbox”.

timu-mu4e-msmtp-select-account

When sending emails, which use the CLI program msmtp in my configuration, the ‘from’ address should be pre-populated with the correct sender address.

(defun timu-mu4e-msmtp-select-account ()
  "Select the right account/context according to the from line."
  (if (message-mail-p)
      (save-excursion
        (let*
            ((from (save-restriction
                     (message-narrow-to-headers)
                     (message-fetch-field "from")))
             (account
              (cond
               ((string-match timu-personal-icloud-email from) "icloud")
               ((string-match timu-personal-aimebertrand-email from) "aimebertrand")
               ((string-match timu-personal-moclub-email from) "moclub"))))
          (setq message-sendmail-extra-arguments (list '"-a" account))))))

Edits

2024-11-09 - Adding/Fixing a function to save all attachments

You can find this in the new post Mu4e - Update - save attachments faster.