Taking email offline II
In the last post I outlined the thought process that led to my current mail setup, and I have a high-level tour of the tools I’m using, including OfflineImap and mu. I wanted to write a follow-up post that gave more details about my configuration, since it was blog posts like this that helped me get started.
OfflineImap configuration
I wanted OfflineImap to sync both my gmail account and my IMAP account at tokle.us. In the [general]
section I name the accounts so that I can refer to them later in the config. The ui
option just specifies how you want OfflineImap to look when you invoke it from the command-line.
[general]
ui = ttyui
accounts = Gmail, Dreamhost
For each account you have to configure the local and remote repository. In the [Account]
section you name these repositories, again, for later reference in the file. I chose to use sqlite for the status cache, which is recommended in the docs. The default is to use a plain text file, which is slow.
[Account Gmail]
localrepository = Gmail-Local
remoterepository = Gmail-Remote
status_backend = sqlite
For the local repository, there isn’t much to set up. I want to store messages locally in Maildir format, and I want to store them in a subdirectory of ~/Mail to keep them separate from my Dreamhost email.
[Repository Gmail-Local]
type = Maildir
localfolders = /home/joshua/Mail/Gmail
For the remote repository, OfflineImap has a type = Gmail
option which presumably takes care of some bookkeeping (we don’t have to specify the address of gmail’s mail server, for example). I’m currently keeping my mail password in plaintext in the configuration file, although OfflineImap allows for more secure setups. I set the maximum number of connections to 3 to speed up syncs, and I point to my local certificate file so OfflineImap can establish a secure connection (this is probably required for gmail). Finally, I only want to sync my gmail inbox so I specify the folderfilter
option. This option takes a python function which acts on a folder name to determine if the folder should be synced or not. In my case the function is trivial, but you can write things like lambda f: f.startswith("Com")
and OfflineImap would know to only sync your folders named Computers, Community, and Combustible.
I’m only syncing my inbox because gmail does some weird thing where tags become IMAP folders, and initially I was getting some UID validity errors. These two things are probably unrelated—I get the impression that a lot of users sync everything from gmail—I just decided that everything I want is in the inbox. I was never consistent about tagging messages anyway, and I can’t remember the last time I had to dig something out of the bulk or spam folder. (edit: I ended up going back and syncing sent mail too: without it, some old email threads were incomplete.)
Note that I’m not using OfflineImap’s autorefresh option to periodically check for updates. Nor did I set up a cron job or systemd unit. For now, I’m relying on mu4e to periodically check mail. Since I’m not keeping the connection alive, each sync will not be as fast as possible, but this reduces the number of places I need to configure my system.
[Repository Gmail-Remote]
type = Gmail
remoteuser = jtokle@gmail.com
remotepass = ########
maxconnections = 3
sslcacertfile = /etc/ssl/certs/ca-certificates.crt
folderfilter = lambda f: f == 'INBOX'
The configuration for my Dreamhost account and local repository are the same as for gmail.
[Account Dreamhost]
localrepository = Dreamhost-Local
remoterepository = Dreamhost-Remote
status_backend = sqlite
[Repository Dreamhost-Local]
type = Maildir
localfolders = /home/joshua/Mail/Dreamhost
For the remote repository, I can’t just point to the certificate file as before, because mail.tokle.us redirects to something under *.mail.dreamhost.com and I was getting certificate mismatch errors. Instead, I specify the certification fingerprint that I expect to see when I connect.
[Repository Dreamhost-Remote]
type = IMAP
remotehost = mail.tokle.us
remoteuser = joshua@tokle.us
remotepass = ########
maxconnections = 3
ssl = yes
cert_fingerprint = b985a8c4f8b6757abf379613bb78331e9cfb3a57
mu4e configuration
Now I’ll show you my configuration for mu4e, the emacs-based mail client built on mu. First, I decided to split my mu4e configuration out from my init.el so that I could run with slightly different options when starting mu4e from a terminal.
;; init.el
(add-to-list 'load-path "~/.emacs.d/elisp/")
(load-library "mu4e-config")
The rest of the configuration is in the file mu4e-config.el in ~/.emacs/elisp. To start, I load mu4e and I specify the folders I want to use for various actions.
;; ~/.emacs/elisp/mu4e-config.el
(require 'mu4e)
(setq
mu4e-maildir "/home/joshua/Mail"
mu4e-drafts-folder "/Dreamhost/INBOX.Drafts"
mu4e-sent-folder "/Dreamhost/INBOX.Sent"
mu4e-trash-folder "/Dreamhost/INBOX.Trash"
mu4e-refile-folder "/Dreamhost/INBOX.Archive")
By specifying mu4e-trash-folder
, for example, I’m telling mu4e to move messages marked for deletion to /Dreamhost/INBOX.Trash. Next, I specify that I want to run OfflineImap every 300 seconds to check for new mail.
(setq
mu4e-get-mail-command "offlineimap"
mu4e-update-interval 300)
The next directives tell mu4e (actually, a separate emacs mode called message-mode, which mu4e relies on) how to send mail. I’m using the msmtp program, which uses the same command-line arguments as sendmail but connects to remote smtp server. The setup here tells msmtp to look at the “from” field on the message to determine which account to send the mail through.
(setq
message-send-mail-function 'message-send-mail-with-sendmail
message-sendmail-extra-arguments '("--read-envelope-from")
message-sendmail-f-is-evil 't
sendmail-program "/usr/bin/msmtp")
By writing a little about myself, those fields will be filled in when I go to compose a message.
(setq
mail-host-address "tokle.us"
user-full-name "Joshua Tokle"
user-mail-address "joshua@tokle.us")
Finally, I request html when its available, after piping it through the external program html2text. By default, html2text uses backspaces and underscores to draw underlines. This doesn’t work in emacs, so I pass the -nobs
flag to prevent this behavior.
(setq
mu4e-view-prefer-html 't
mu4e-html2text-command "html2text -nobs")
New mail notifications
I’ve adapted this solution from the mu-discuss mailing list to show notifications when new mail arrives. First, in mu4e-config.el, I use the index-updated hook to call a shell script named youve_got_mail and pass as an argument the number of seconds between updates.
;; mu4e-config.el
(add-hook 'mu4e-index-updated-hook
(lambda ()
(shell-command (concat "youve_got_mail "
(number-to-string mu4e-update-interval)))))
The youve_got_mail script does three things: it computes the timestamp of the previous mail sync, it counts the number of unread messages that are newer than the timestamp, and it uses libnotify to display a notification if that number is nonzero. Here’s the script:
#!/bin/sh
# timestamp of previous mail sync
NBACK=$(date +%s --date="$1 sec ago")
# number of messages after timestamp
NMAIL=$(mu find flag:unread --after=$NBACK 2>/dev/null | wc -l)
if [ $NMAIL -eq 1 ]
then
notify-send "1 new message"
elif [ $NMAIL -gt 1 ]
then
notify-send "$NMAIL new messages"
fi
mu4e in the terminal
I also wrote a minimal emacs config that I can use to launch mu4e in the terminal. It just sets up a console-friendly color scheme, hides the menu bar, and launches mu4e:
;; mu4e-terminal.el
(add-to-list 'custom-theme-load-path "~/.emacs.d/elpa/bubbleberry-theme-0.1.2")
(load-theme 'bubbleberry t)
(tool-bar-mode -1)
(menu-bar-mode -1)
(add-to-list 'load-path "/usr/share/emacs/site-lisp/mu4e/")
(add-to-list 'load-path "~/.emacs.d/elisp/")
(load-library "mu4e-config")
(mu4e)
I then added the following alias to my .zshenv.
alias mu4e='emacs -Q -nw -l ~/.emacs.d/elisp/mu4e-terminal.el'
It looks like this.