mu4easy icon indicating copy to clipboard operation
mu4easy copied to clipboard

mu4e + mbsync configuration for multiple email accounts.

#+TITLE: mu4easy

#+html: GNU Emacs #+html: MELPA #+html:

A global minor mode that defines a full working setup for mu4e and =mbsync=, based on mu 1.8+. Easily setup accounts and aliases from these providers: Google, Apple, GMX and Proton. Additional packages are installed and configured for a better experience, including =mu4e-column-faces=, =mu4e-alert=, =org-msg= and =helm-mu=. Some customizations are available, run =(customize-group 'mu4easy)=.

Install locally and use the =load-path= variable, e.g.: #+begin_src elisp (use-package mu4easy :load-path "~/Code/mu4easy" :bind ("C-c u" . mu4e) :config (mu4easy-mode)) #+end_src

Or install with Melpa. Here is an example with one email account: #+begin_src elisp (use-package mu4easy :ensure t :bind ("C-c u" . mu4e) :config (mu4easy-mode) :custom (mu4easy-contexts '((mu4easy-context :c-name "Google" :maildir "Gmail" :mail "[email protected]" :smtp "smtp.gmail.com" :sent-action delete)))) #+end_src See later more details on the =mu4easy-context= macro.

Make sure you have =mu= in your in PATH.

  • mbsync Configuration
    • [[#Google][Google]]
    • [[#Apple][Apple]]
    • [[#GMX][GMX]]
    • [[#Proton][Proton]]
  • [[#mu4e][mu4e configuration]]

Comments and suggestions are welcome.

** mbsync :PROPERTIES: :CREATED: [2021-05-09 Sun 22:39] :END:

The important part is to normalize all accounts to have the same structure, containing the folders Inbox, Archive, Sent, Trash, Drafts and Spam. I would advice against syncing the Drafts folder because it can lead to issues. If you decide not to sync it, just remove the relevant lines from the =.mbsyncrc= file.

Let's go over each provider and examine the gotchas.

*** Google :PROPERTIES: :CREATED: [2021-05-09 Sun 22:42] :END:

#+begin_src conf IMAPAccount Gmail Host imap.gmail.com User [email protected] PassCmd "gpg -q --for-your-eyes-only --no-tty -d ~/.authinfo.gpg | awk '/machine imap.gmail.com login [email protected]/ {print $NF}'" AuthMechs LOGIN SSLType IMAPS SSLVersions TLSv1.2 CertificateFile /usr/local/etc/[email protected]/cert.pem

MaildirStore Gmail-local Path ~/Mail/Gmail/ Inbox ~/Mail/Gmail/Inbox SubFolders Verbatim

IMAPStore Gmail-remote Account Gmail

Channel Gmail-inbox Far :Gmail-remote:"INBOX" Near :Gmail-local:"INBOX" CopyArrivalDate yes Create Both Expunge Both SyncState *

Channel Gmail-trash Far :Gmail-remote:"[Gmail]/Trash" Near :Gmail-local:"Trash" CopyArrivalDate yes Create Both Expunge Both SyncState *

Channel Gmail-spam Far :Gmail-remote:"[Gmail]/Spam" Near :Gmail-local:"Spam" CopyArrivalDate yes Create Both Expunge Both SyncState *

Channel Gmail-all Far :Gmail-remote:"[Gmail]/All Mail" Near :Gmail-local:"Archive" CopyArrivalDate yes Create Both Expunge Both SyncState *

Channel Gmail-drafts Far :Gmail-remote:"[Gmail]/Drafts" Near :Gmail-local:"Drafts" CopyArrivalDate yes Create Both Expunge Both SyncState *

Group Gmail Channel Gmail-inbox Channel Gmail-trash Channel Gmail-all Channel Gmail-spam Channel Gmail-drafts #+end_src

First is the =PassCmd=. I assume all passwords (and application specific passwords) are in =~/.authinfo.gpg= with the password key-value pair last (see the =NF= variable in =awk= pointing to the last column).

Next, Google has the structure =[Gmail]/...= so we use explicit far/near definitions. We'll drop them in the next providers.

Finally, unlike other providers, we're not going to sync the Sent folders because Google is saving all email in the All Mails folders and you'll end up with duplicates locally.

*** Apple :PROPERTIES: :CREATED: [2021-05-09 Sun 22:46] :END: #+begin_src conf IMAPAccount Apple Host imap.mail.me.com PORT 993 User [email protected] PassCmd "gpg -q --for-your-eyes-only --no-tty -d ~/.authinfo.gpg | awk '/machine imap.mail.me.com/ {print $NF}'" AuthMechs LOGIN SSLType IMAPS SSLVersion TLSv1.2 CertificateFile /usr/local/etc/[email protected]/cert.pem

MaildirStore Apple-local Path ~/Mail/Apple/ Inbox ~/Mail/Apple/Inbox SubFolders Verbatim

IMAPStore Apple-remote Account Apple

Channel Apple-all Far :Apple-remote: Near :Apple-local: Patterns "INBOX" "Archive" "Trash" "Spam" "Drafts" CopyArrivalDate yes Create Both Expunge Both SyncState *

Channel Apple-sent Far :Apple-remote:"Sent Messages" Near :Apple-local:"Sent" CopyArrivalDate yes Create Both Expunge Both SyncState *

Group Apple Channel Apple-sent Channel Apple-all #+end_src

Here we use the =Patterns= key to quickly select the folders we're interested in. It turns the Sent folders has many conventions; at Apple it's called Sent Messages.

*** GMX :PROPERTIES: :CREATED: [2021-05-09 Sun 22:48] :END:

#+begin_src conf IMAPAccount GMX Host imap.gmx.com User [email protected] PassCmd "gpg -q --for-your-eyes-only --no-tty -d ~/.authinfo.gpg | awk '/machine imap.gmx.com login [email protected]/ {print $NF}'" AuthMechs LOGIN SSLType IMAPS SSLVersion TLSv1.2 CertificateFile /usr/local/etc/[email protected]/cert.pem

MaildirStore GMX-local Path ~/Mail/GMX/ Inbox ~/Mail/GMX/Inbox SubFolders Verbatim

IMAPStore GMX-remote Account GMX

Channel GMX Far :GMX-remote: Near :GMX-local: Patterns "INBOX" "Archive" "Trash" "Spam" "Drafts" "Sent" CopyArrivalDate yes Create Both Expunge Both SyncState * #+end_src

*** Proton :PROPERTIES: :CREATED: [2021-05-09 Sun 22:49] :END:

#+begin_src conf IMAPAccount Proton Host 127.0.0.1 PORT 1111 User [email protected] PassCmd "gpg -q --for-your-eyes-only --no-tty -d ~/.authinfo.gpg | awk '/machine 127.0.0.1/ {print $NF}'" AuthMechs LOGIN SSLType STARTTLS SSLVersion TLSv1.2 CertificateFile /usr/local/etc/[email protected]/cert.pem

MaildirStore Proton-local Path ~/Mail/Proton/ Inbox ~/Mail/Proton/Inbox SubFolders Verbatim

IMAPStore Proton-remote Account Proton

Channel Proton Far :Proton-remote: Near :Proton-local: Patterns "INBOX" "Archive" "Trash" "Spam" "Drafts" "Sent" CopyArrivalDate yes Create Both Expunge Both SyncState * #+end_src

In order to us Proton, one needs to install a bridge application. It specifies the IMAP and SMTP ports to use (non-standard).

** mu4e :PROPERTIES: :CREATED: [2021-05-09 Sun 22:53] :END:

Let's go over the important parts of the elisp code.

#+begin_src elisp (setf (alist-get 'trash mu4e-marks) '(:char ("d" . "▼") :prompt "dtrash" :dyn-target (lambda (target msg) (mu4e-get-trash-folder msg)) ;; Here's the main difference to the regular trash mark, no +T ;; before -N so the message is not marked as IMAP-deleted: :action (lambda (docid msg target) (mu4e--server-move docid (mu4e--mark-check-target target) "+S-u-N")))) #+end_src

I picked this code and realized that, at least for Google, if you flag a message =trashed=, it just disappears. This code instead flags the message as =seen=, removes the flags =unseen= and =new= and finally, moves it to the Trash folder, which is synced to the server and gets deleted according to a policy you control.

#+begin_src elisp (cl-defmacro mu4easy-context (&key c-name maildir mail smtp (smtp-mail mail) (smtp-port 587) (smtp-type 'starttls) (sent-action 'sent) (name "Daniel Fleischer") (sig "Daniel Fleischer")) (let ((inbox (concat "/" maildir "/Inbox"))
(sent (concat "/" maildir "/Sent")) (trash (concat "/" maildir "/Trash")) (refile (concat "/" maildir "/Archive")) (draft (concat "/" maildir "/Drafts")))

`(make-mu4e-context
  :name ,c-name
  :match-func (lambda (msg)
                (when msg
                  (string-match-p (concat "^/" ,maildir "/")
                                  (mu4e-message-field msg :maildir))))
  :vars '((user-mail-address . ,mail)
          (user-full-name . ,name)
          (mu4e-sent-folder . ,sent)
          (mu4e-drafts-folder . ,draft)
          (mu4e-trash-folder . ,trash)
          (mu4e-refile-folder . ,refile)
          (mu4e-compose-signature . (concat ,sig))
          (mu4e-sent-messages-behavior . ,sent-action)
          (smtpmail-smtp-user . ,smtp-mail)
          (smtpmail-starttls-credentials . ((,smtp ,smtp-port nil nil)))
          (smtpmail-auth-credentials . '((,smtp ,smtp-port ,smtp-mail nil)))
          (smtpmail-default-smtp-server . ,smtp)
          (smtpmail-smtp-server . ,smtp)
          (smtpmail-stream-type . ,smtp-type)
          (smtpmail-smtp-service . ,smtp-port)
          (org-msg-signature . ,sig)
          (mu4e-maildir-shortcuts . 
                                  ((,inbox   . ?i)
                                   (,sent    . ?s)
                                   (,trash   . ?t)
                                   (,refile  . ?a)
                                   (,draft   . ?d)))))))

#+end_src

That's the macro to create contexts or identities. It's assuming you have a consistent maildirs structure, like specified in the =mbsync= config, i.e. all account names are on a single level and then below them you have Inbox, Archive, Trash, Sent, Spam and Drafts for each one. To match the context I'm just looking at the maildir the message is in. Some defaults in the function are the SMTP encryption and what to do with sent messages (either delete them in the case of Google or save them in the Sent folder; more on that in the contexts examples).

Next there are some variables settings; these are set to taste, feel free to experiment with them. Next are the bookmarks, which are very convenient both for jumping and for reading the read/unread counts.

Tip: the bookmarks query can be either a function or a string. If it's a function, there is no read/unread count. I'm using a string generated from a function; if you first eval the string and then set the variable, you do get counts.

For org-msg package users, notice that the package itself handles the signature, so you want to define ~org-msg-signature~ like I did in the macro. It accepts =org= formatting, e.g. ~Daniel Fleischer\n/Skynet Inc/~ and then converts it into formatted HTML. Also, when using org-msg, reply style is /top-posting/ so you need it to handle the signature correctly (above the replied text).

Another improvement is creating a customized link description; i.e. calling ~org-store-link~ to save a link to an email, it uses ~mu4easy-mail-link-description~ which will give a nice description of the form =to/from: subject (ISO timestamp)= - works with =org-capture= as well.

Added is a custom updating function that asks you which account to update, or by default updates all. It is bound to the usual "U".

Finally, setting up the accounts, either using customization or using code: #+begin_src elisp (setq mu4easy-contexts

  '((mu4easy-context
     :c-name  "Google"
     :maildir "Gmail"
     :mail    "[email protected]"
     :smtp    "smtp.gmail.com"
     :sent-action delete)
    
    (mu4easy-context
     :c-name  "1-GMX"
     :maildir "GMX"
     :mail    "[email protected]"
     :smtp    "mail.gmx.com")
    
    (mu4easy-context
     :c-name    "2-GMX-alias"
     :maildir   "GMX"
     :mail      "[email protected]"
     :smtp      "mail.gmx.com"
     :smtp-mail "[email protected]")
    
    (mu4easy-context
     :c-name  "Apple"
     :maildir "Apple"
     :mail    "[email protected]"
     :smtp    "smtp.mail.me.com")
    
    (mu4easy-context
     :c-name  "3-Apple-alias"
     :maildir "Apple"
     :mail    "[email protected]"
     :smtp    "smtp.mail.me.com"
     :smtp-mail "[email protected]")
    
    (mu4easy-context
     :c-name    "Proton"
     :maildir   "Proton"
     :mail      "[email protected]"
     :smtp      "127.0.0.1"
     :smtp-type ssl
     :smtp-port 999)
    
    (mu4easy-context
     :c-name    "4-Proton-alias"
     :maildir   "Proton"
     :mail      "[email protected]"
     :smtp      "127.0.0.1"
     :smtp-mail "[email protected]"
     :smtp-type ssl
     :smtp-port 999)))

#+end_src

Important points:

  1. Jumping to contexts is based on their first (unique) letter, that's why I'm using numbers in the =c-name= key.
  2. Google saves the sent messages in the All Mail (Archive) folder so it is recommended to set the corresponding mu4e setting to delete sent messages (locally). It's only for Google; for the other account, sent messages are saved in the Sent folder.
  3. The =2-GMX= account is an alias - not another GMX account (see the last comment). It has a different mail, but the SMTP authentication needs the real email address. The outgoing email still looks like it is coming from the alias.
  4. Proton account needs SSL encryption for SMTP, it connects to =localhost= and uses non-standard ports for IMAP and SMTP (check the Proton bridge app for details).
  5. If you have multiple accounts with the same providers, they should have different maildirs, e.g. =~/Mail/Gmail1/=, =~/Mail/Gmail2/=. Here I showed aliases, not multiple accounts.

** Disclaimer :PROPERTIES: :CREATED: [2021-05-09 Sun 22:32] :ID: 2AECA758-B861-446B-B73E-C34DDF6EBD3F :END:

This setup is based upon a couple of weeks worth of tweaking, trial and error. It's not perfect; some email were lost (those not saved into =sent=), lessons were learned. I didn't try it with Microsoft-based emails. Make sure to test everything you do - to see that emails are going in and out, saved in =Archive= and =Sent=, compare the results with the web-based interface until you get comfortable using it 100% of the time. Or not.