Couple months ago I decided to reduce the number of online services I’m using and depend on my local machine setup, My local setup is linux on all machines (Except for the gaming PC) so it would be possible to get rid of the Gmail interface for example in favor or a local application.
As I depend on Emacs as an editor and other tasks I wanted to try using it as an email client, The way I used was “Mu4e” which is a package that can read/send emails from emacs interface, here is how I had this setup working.
Downloading all emails locally with OfflineIMAP ¶
Offline IMAP is a python program that can connect to many IMAP servers and sync your emails from the IMAP servers to local machine directory. which means I’ll have all my email offline I can search, read and move them around like any other file on the machine.
Offline IMAP is part of Archlinux community packages so install it with
sudo packman -S offlineimap
Then I needed to configure it to sync the emails to
~/.offlineimaprc, the content for me is as follows
1[general] 2accounts = account1, account2 3pythonfile = ~/path/to/mailpass.py 4maxsyncaccounts = 2 5 6[Account account1] 7localrepository = account1-local 8remoterepository = account1-remote 9autorefresh = 5 10postsynchook = mu index 11 12[Repository account1-local] 13type = Maildir 14localfolders = ~/mail/account1 15 16[Repository account1-remote] 17type = Gmail 18remoteuser = firstname.lastname@example.org 19remotepasseval = get_pass("~/.ssh/account1.gmail.password") 20sslcacertfile = /etc/ssl/certs/ca-certificates.crt 21ssl_version = tls1_2 22 23[Account account2] 24localrepository = account2-local 25remoterepository = account2-remote 26autorefresh = 5 27postsynchook = mu index 28 29[Repository account2-local] 30type = Maildir 31localfolders = ~/mail/account2 32 33[Repository account2-remote] 34type = Gmail 35remoteuser = email@example.com 36remotepasseval = get_pass("~/.ssh/account2.password") 37sslcacertfile = /etc/ssl/certs/ca-certificates.crt 38ssl_version = tls1_2
This configuration uses a password encrypted in a file with my rsa private key, that gets decrypted with a pythong function defined in
So I get an application specific password for my email account from my gmail account then I encrypt it to a file like so:
1echo -n "<password>" | openssl rsautl -inkey ~/.ssh/id_rsa -encrypt > ~/path/to/email.password
~/path/to/mailpass.py is a python file that has one function it takes a file path and decrypt it with openssl
1#! /usr/bin/env python2 2from subprocess import check_output 3 4def get_pass(file): 5 return check_output("cat " + file + "| openssl rsautl -inkey ~/.ssh/id_rsa -decrypt", shell=True).splitlines()
IMAP uses this
get_pass() function to get the password, this is an alternative to writing your password directly in the
offlineimap command should pickup the configuration and start syncing it to
To make sure offlineimap runs everytime I login to my user account I enable and start the systemd service that comes with offlineimap package
1systemctl enable offlineimap --user 2systemctl start offlineimap --user
You’ll notice that offlineimap configuration we added this line
postsynchook = mu index
Using Mu to index the emails ¶
which will run
mu index command after every sync, mu is a program that uses Xapian to build a full text search database for the email directory, then you can use
mu to search in your emails,
mu comes with
Mu4e which is the emacs interface for mu.
mu package is in archlinux AUR under the name
mu so you can install it with the AUR helpe you have, I’m using
yay so the command for me is:
1yay -S mu
Doing that will make offlimeimap sync the emails every 5 minutes and then invokes
mu index to rebuild the database.
You can use
mu to ask for new emails now, for example I have a script that will display the number of unread emails in my
1mu find 'flag:unread AND (maildir:/account1/INBOX OR maildir:/account2/INBOX)' 2> /dev/null | wc -l
This will query for unread emails in the INBOX directories in each account and then count the number of lines in the output.
I have this line in my polybar configuration which is unobtrusive way to know if I have any new emails, instead of the annoying notifications.
Now we’ll need to have our Mu4e interface setup in Emacs so we can read new emails.
For spacemacs users you should add the layer in your
~/spacemacs layers list
1(mu4e :variables 2 mu4e-use-maildirs-extension t 3 mu4e-enable-async-operations t 4 mu4e-enable-notifications t)
And then I load a file called
mu4e-config.el, in the
;;;additional files section I require it
The file lives in
~/dotfiles/emacs/mu4e-config.el and I push this directory
path to the load-path of emacs with
1(push "~/dotfiles/emacs/" load-path)
The file will hold all of our Mu4e configuration
1(provide 'mu4e-config) 2 3(require 'mu4e-contrib) 4 5(spacemacs/set-leader-keys "M" 'mu4e) 6 7(setq mu4e-inboxes-query "maildir:/account1/INBOX OR maildir:/account2/INBOX") 8(setq smtpmail-queue-dir "~/mail/queue/cur") 9(setq mail-user-agent 'mu4e-user-agent) 10(setq mu4e-html2text-command 'mu4e-shr2text) 11(setq shr-color-visible-luminance-min 60) 12(setq shr-color-visible-distance-min 5) 13(setq shr-use-colors nil) 14(setq mu4e-view-show-images t) 15(setq mu4e-enable-mode-line t) 16(setq mu4e-update-interval 300) 17(setq mu4e-sent-messages-behavior 'delete) 18(setq mu4e-index-cleanup nil) 19(setq mu4e-index-lazy-check t) 20(setq mu4e-view-show-addresses t) 21(setq mu4e-headers-include-related nil) 22 23(advice-add #'shr-colorize-region :around (defun shr-no-colourise-region (&rest ignore))) 24 25(with-eval-after-load 'mu4e 26 (mu4e-alert-enable-mode-line-display) 27 (add-to-list 'mu4e-bookmarks 28 '(:name "All inboxes" 29 :query mu4e-inboxes-query 30 :key ?i)) 31 32 (setq mu4e-contexts 33 `( ,(make-mu4e-context 34 :name "account1" 35 :match-func (lambda (msg) (when msg (string-prefix-p "/account1" (mu4e-message-field msg :maildir)))) 36 :vars '( 37 (mu4e-sent-folder . "/account1/[Gmail].Sent Mail") 38 (mu4e-drafts-folder . "/account1/[Gmail].Drafts") 39 (mu4e-trash-folder . "/account1/[Gmail].Trash") 40 (mu4e-refile-folder . "/account1/[Gmail].All Mail") 41 (user-mail-address . "firstname.lastname@example.org") 42 (user-full-name . "Emad Elsaid") 43 (mu4e-compose-signature . (concat "Emad Elsaid\nSoftware Engineer\n")) 44 (smtpmail-smtp-user . "account1") 45 (smtpmail-local-domain . "gmail.com") 46 (smtpmail-default-smtp-server . "smtp.gmail.com") 47 (smtpmail-smtp-server . "smtp.gmail.com") 48 (smtpmail-smtp-service . 587) 49 )) 50 ,(make-mu4e-context 51 :name "account2" 52 :match-func (lambda (msg) (when msg (string-prefix-p "/account2" (mu4e-message-field msg :maildir)))) 53 :vars '( 54 (mu4e-sent-folder . "/account2/[Gmail].Sent Mail") 55 (mu4e-drafts-folder . "/account2/[Gmail].Drafts") 56 (mu4e-trash-folder . "/account2/[Gmail].Trash") 57 (mu4e-refile-folder . "/account2/[Gmail].All Mail") 58 (user-mail-address . "email@example.com") 59 (user-full-name . "Emad Elsaid") 60 (mu4e-compose-signature . (concat "Emad Elsaid\nSoftware Engineer\n")) 61 (smtpmail-smtp-user . "account2") 62 (smtpmail-local-domain . "gmail.com") 63 (smtpmail-default-smtp-server . "smtp.gmail.com") 64 (smtpmail-smtp-server . "smtp.gmail.com") 65 (smtpmail-smtp-service . 587) 66 )) 67 )))
The previous configuration will define 2 different context each for every email account, each context we tell mu4e the directories for sent, draft, trash and archive directories.
We also told mu4e which smtp servers to use for each context. without it we’ll be able to read the emails but not send any emails. You may have noticed that this configuration doesn’t have the password for the SMTP servers.
Emacs uses a file called
~/.authinfo to connect to remote servers, the SMTP servers are not different for emacs, when
mu4e tries to connect to the SMTP server over port 587, emacs will use the credentials in this file to connect to it.
The file content should look like this:
machine smtp.gmail.com port 587 login account1 password <password1> machine smtp.gmail.com port 587 login account2 password <password2>
We also defined a new bookmark that shows us the inbox emails across all accounts, it’s bound to
And we changed bound the Mu4e interface to
SPC M where
SPC is my leader key in spacemacs configuration.
How to use this setup ¶
So now this is my workflow:
- offlineimap works in the background syncing my email to
- When I see the unread email count in my status bar I switch to emacs
- I press
SPC Mto open Mu4e
- I press
bito open the all inbox emails bookmark
- I open the email with
RETand archive it with
- When I go through all my unread emails list I press
xto archive all emails
- I press
qcouple times to continue what I was doing in emacs
Searching through emails:
- I open Mu4e with
sto search, I write a word I remember about this email then
- I go through the emails with
- When I’m done reading the email I was searching for I quit the email interface with
- I open Mu4e with
- Start a new compose buffer
- Fill in the
subjectfields and the message body
, cto send it or
, kto discard it.
Bonus step to sync across machines ¶
Syncing across machines works when imap sync one machine to the remove IMAP server and the other machine do the same, if you want to make it a bit faster you can use syncthing to sync the