It has been 18 months that I read & write my emails in Emacs. No need to say I have enjoyed
the mouse-free experience brought by Emacs. Recently, I had to keep track a new
email account. So I came across my old note written back then which I enhanced
in this post. I made lots of updates subsequently including password management
through pass
, multi-accounts support etc. to make it as complete as possible.
Introduction
A full back-and-forth cycle of email consists to
- Receive email through a program which synchronize emails locally from an email server.
- Read email through a program (MUA) whose the UI offers an organized & handy presentation of emails.
- Compose email in whatever editor.
- Send email with a mail transfer agent or an interface of it.
I made the following choices which I will detail the configuration throughout this post:
- Receive email: offlineimap.
- Read email: notmuch.el.
- Compose email: Emacs message mode.
- Send email: smtpmail-multi to send email through multiple SMTP servers.
As you may have seen, except the reception of email, the remaining can be done within Emacs.
Receiving email
As stated above, we use offlineimap
to fetch emails from potentially multiple
mailbox. But most importantly, we store all emails locally for two purposes:
1. to be able to read emails offline, 2. to not mess up tags synchronization
which may cause data loss. You may think that your huge mailbox would take a
tremendous place in your disk. Well, I can say that if you pay attention to
keep only one copy of your emails1, it should not take much. For instance,
I have 2.6k emails taking 460MB of the disk.
Configure offlineimap
Install offlineimap
with your favorite package manager2. Then copy
the minimal configuration (the path depends on your distribution).
|
|
Here is the relevant part of my configuration (~/.offlineimaprc
) for reference.
See the documentation in /usr/share/doc/offlineimap/examples/offlineimap.conf
or Archwiki for more information. Note the postsynchook
option at the account
level: it’s an email tagging script which is run as soon as new email arrives. We
will come soon to its content in this section. Remember what I said regarding
the space taken by locally stored email? Well, they remain tiny provided that
they are not duplicated elsewhere. That’s not always the case, for instance
Gmail may store an email in the folder [Gmail].Important
beside [Gmail].All Mail
.
You may consider to filter out the extra folders you don’t want as below with a
python’s lambda expression or a function (see the documentation).
|
|
Launch offlineimap automatically at boot
You would certainly want to launch automatically offlineimap
at boot.
This can be done with systemd
. In my case, I have three accounts, it’s
advised 3 to create three separated systemd
services and set maxsyncaccounts = 1
in ~/.offlineimaprc
as we have done above.
Instead of write three different system service files, I write the following
template unit file where the variable %i
will match later with an account
name in .offlineimaprc
. (Note that you should avoid “@” in the account name
since systemd
gives it a precise meaning).
|
|
Then run systemctl daemon-reload
to load the new service file.
The following commands enable the auto-start on boot and launch the service right now.
|
|
Note that account-*
is the account name appeared in each [ Account XXX ]
section.
If everything goes well, offlineimap
will sync emails on the next boot automatically.
Auto-tagging with notmuch
The next thing to do is email auto-tagging, without this feature your mailbox will be a nightmare. Again, install notmuch with your favorite package manager. We will write the script aforementioned so that email be filtered as soon as they are synced locally.
Configurate notmuch
Before starting to use notmuch, you must configure it. In particular, you have to set the
database path, your email accounts which appeared in ~/.offlineimaprc
,
tagging rule for incoming email and tag to exclude by default when searching.
|
|
Expose highly active addresses
The following command lists email-senders address sorted by decreasing amounts of emails sent4.
|
|
Replace From
by To
to expose highly active mailing list.
The snippet above help us to find out the best contributors of our inbox to tag them properly.
Auto-tagging script
Here is my little shell script ~/.email/postsync.sh
which is run once offlineimap
finished to sync my emails. You might want to take a look at notmuch help search-terms
to understand the syntax of tagging commands.
I identify several visibility categories of emails:
- I don’t want to see them at all and they’re harmful => spam
- I don’t want to see them at all => blacklisted
- I want to see them but it doesn’t matter when => move out inbox
- It’s important! => keep them in the inbox and tag them more
|
|
Reading mail
notmuch.el
Follow the instructions given on the official website.
Key-bindings
The key-bindings I use are from evil-collection. They are quite different from the default ones.
You can define new keybindings for different notmuch views (tree
, show
, hello
,
search
, message
) as below, but usually I rarely tag manually an email (except flagging important one).
Instead, I add a new tagging rule as depicted above.
|
|
Compose email
Simply press C-x m
(compose-mail
) in Emacs to compose an email to send. Normally, the
From:=/=To:
fields can be autocompleted.
Send email
Unless your local system is configured for sending email using sendmail, you may want to access a remote SMTP server.
SMTP configuration
Below is a fragment of my SMTP setup. You should acquire this information from
the host (Gmail5, your institution, your company etc.). Using smtpmail
is not
enough to sending email with different accounts. Fortunately, the package
smtpmail-multi made the task easier.
|
|
Then you have to put your credentials somewhere. Such places are designated by
the variable auth-sources
which defaults to ("~/.authinfo" "~/.authinfo.gpg" "~/.netrc")
.
For instance, put the following in ~/.authinfo
.
|
|
Patch: Fully-Qualified Domain Name (FQDN)
You may encounter issue regarding the FQDN when sending email. I have the following patch in my configuration coming from here.
|
|
Bonus: passwords encryption with pass
You may have seen a security hole which would hopefully make you uncomfortable: we have written credentials in plain text. Let’s fix it. I assume in the following that the reader has already setup gpg (2.1+) and pass.
Remember, we have stored passwords in ~/.offlineimaprc
to pull emails locally
with offlineimap
and in ~/.authinfo
so that Emacs is able to send email.
~/.offlineimaprc
Quoting ArchWiki:
-
Create a password for your email account.
1
pass insert email/myaccount
-
Create a python function that retrieves the password (in
~/.offlineimap/pass.py
for instance).1 2 3 4 5
#! /usr/bin/env python3 from subprocess import check_output def get_pass(account): return check_output("pass email/" + account, shell=True).splitlines()[0]
-
In
.offlineimaprc
, under the general section, indicate the python file1 2 3
[general] # ... pythonfile = ~/.offlineimap/pass.py
and replace each
remotepass = password
by the next one.1
remotepasseval = get_pass("myaccount")
auth-source-pass
To make Emacs read credentials through pass
, we use the package
auth-source-pass which exactly do the job for us. The configuration is simple.
|
|
You should create a <smtp host>.gpg
with pass --edit email/<smtp host>
for
each smtp server. For instance, the entry of .authinfo
|
|
corresponds to ~/.password-store/email/smtp.host.fr.gpg
|
|
Cache gpg passphrase
By now, offlineimap
and Emacs will retrieve your passwords through
pass
. Great! But, if you setup gpg
without extra configuration, you will be
prompted the passphrase every two hours. Why? The answer lies in the gpg agent
options default-cache-ttl
and --max-cache-ttl
. The documentation says
|
|
That is, by default, the passphrase is cached 10 minutes and can be extended
each time it is accessed up to 2 hours. As we set RestartSec=60
in
~/.config/systemd/user/offlineimap@.service
, it ensures that we reach the
maximum cache time. To increase the cache time permanently to one day, add the
line below in ~/.gnupg/gpg-agent.conf
.6
|
|
You should restart gpg-agent
to see the effect (gpg -K
should be enough).
At this point, not only are your passwords secure to some extent, but no one can
see and write emails on your behalf after one day without enter the passphrase.
Addendum: general workflow
I summarize below how one maintains this email workflow.
-
Tagging emails. Update
~/.email/postsync.sh
when necessary (usually to blacklist some addresses). -
Changing password. Modify adequately
~/.offlineimaprc
and.authinfo
, or if you usepass
as above, update the passwords withpass edit email/<account>
. Beware, if you only change your password remotely, you won’t be able to receive and (possibly) write any email. -
Adding new email. Each time you want to add a new email account in you workflow, you should
- update
.offlineimaprc
: updateaccounts
, add one more account section plus
associated local/remote sections;
- update
.notmuch-config
: updateprimary_email
orother_email
; - update
smtpmail-multi-accounts
andsmtpmail-multi-associations
if that account may be used to write email; - update credentials with
pass
; - run
systemctl enable --user --now offlineimap@ACCOUNT-NAME.service
; - (optional) update
postsync.sh
to tag the email written by yourself.
- update
-
After setting up
offlineimap
correctly, you can check this information by runningfdupes -mr .
under~/.email/
. ↩︎ -
Note that, at the time of writing,
offlineimap
is in the process to port frompython2
(2020-01-01 ⚰️) topython3
, see OfflineIMAP/offlineimap3. ↩︎ -
OfflineIMAP community’s website : No, I’m not using maxconnections ↩︎
-
Technically you can aggregate all duplicate email addresses with
jq
, but you would have to handle the case, comma-separated addresses, and the"First Last <first.last@gmail.com>"
notation. It’s merely an example after all. ↩︎ -
If your Google account has 2-step verification activated, you will likely have to create and use an app password instead of your regular password. ↩︎
-
If you retrieve your emails at a frequency lower than every 10 minutes, then you should also assign the same value of
max-cache-ttl
todefault-cache-ttl
. ↩︎