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.
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 ~/mail
directory, OfflineIMAP config file lives in ~/.offlineimaprc
, the content for me is as follows
[general]
accounts = account1, account2
pythonfile = ~/path/to/mailpass.py
maxsyncaccounts = 2
[Account account1]
localrepository = account1-local
remoterepository = account1-remote
autorefresh = 5
postsynchook = mu index
[Repository account1-local]
type = Maildir
localfolders = ~/mail/account1
[Repository account1-remote]
type = Gmail
remoteuser = account1@gmail.com
remotepasseval = get_pass("~/.ssh/account1.gmail.password")
sslcacertfile = /etc/ssl/certs/ca-certificates.crt
ssl_version = tls1_2
[Account account2]
localrepository = account2-local
remoterepository = account2-remote
autorefresh = 5
postsynchook = mu index
[Repository account2-local]
type = Maildir
localfolders = ~/mail/account2
[Repository account2-remote]
type = Gmail
remoteuser = account2@gmail.com
remotepasseval = get_pass("~/.ssh/account2.password")
sslcacertfile = /etc/ssl/certs/ca-certificates.crt
ssl_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 ~/path/to/mailpass.py
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
The ~/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()[0]
IMAP uses this get_pass()
function to get the password, this is an alternative to writing your password directly in the .offlineimaprc
file.
Now running offlineimap
command should pickup the configuration and start syncing it to ~/mail
, this operation takes a long time depending on the number of emails you have in your mail.
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
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 INBOX
directories
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.
I’m using spacemacs but the configuration for Gnu emacs shouldn’t be different. First we load the Mu4e package/layer.
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
1(require 'mu4e-config)
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 . "account1@gmail.com")
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 . "account2@gmail.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 bi
.
And we changed bound the Mu4e interface to SPC M
where SPC
is my leader key in spacemacs configuration.
So now this is my workflow:
~/mail
SPC M
to open Mu4ebi
to open the all inbox emails bookmarkRET
and archive it with r
x
to archive all emailsq
couple times to continue what I was doing in emacsSearching through emails:
SPC M
s
to search, I write a word I remember about this email then RET
C-j
and C-k
q
Sending email:
SPC M
C
to
and subject
fields and the message body, c
to send it or , k
to discard it.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 ~/mail
directory to your other machines, for me I back it up to my phone and other 2 machines at the same time.