In a long transition away from Google, I made another email account, on an email system that values privacy and doesn’t sell information to advertisers. That means that for the moment, I now have two email accounts. Simultaneously, I thought I would give (Neo)Mutt another go as my email client, along with Offlineimap (grabbing mail), notmuch/alot (indexing mail), msmtp (sending email), pass (managing passwords), vim (writing mail), launchd (scheduling mail sync), and w3m (parsing HTML email).
Offlineimap
This is my current .offlineimaprc
:
general
accounts = riseup, gmail
pythonfile = ~/.offlineimap_pass.py
fsync = true
Account riseup
localrepository = riseup-local
remoterepository = riseup-remote
Repository riseup-local
type = Maildir
localfolders = ~/.mail/riseup
Repository riseup-remote
type = IMAP
ssl = true
sslcacertfile = /usr/local/etc/openssl/cert.pem
remotehost = mail.riseup.net
remoteuser = <EMAIL ADDRESS>
remotepasseval = GetPassRiseup()
Account gmail
localrepository = gmail-local
remoterepository = gmail-remote
Repository gmail-local
type = Maildir
localfolders = ~/.mail/gmail
nametrans = lambda folder: re.sub('drafts', '[Google Mail].Drafts',
re.sub('sent', '[Google Mail].Sent Mail',
re.sub('starred', '[Google Mail].Starred',
re.sub('trash', '[Google Mail].Bin', folder))))
Repository gmail-remote
maxconnections = 3
type = Gmail
ssl = true
sslcacertfile = /usr/local/etc/openssl/cert.pem
remoteuser = <EMAIL ADDRESS>
remotepasseval = GetPassGmail()
realdelete = no
nametrans = lambda folder: re.sub('.*Drafts$', 'drafts',
re.sub('.*Sent Mail$', 'sent',
re.sub('.*Starred$', 'starred',
re.sub('.*Bin$', 'trash', folder))))
folderfilter = lambda folder: folder not in [
'[Google Mail]/Important',
'[Google Mail]/Spam',
'[Google Mail]/Chats',
'[Google Mail]/All Mail',
]
createfolders = True
postsynchook = notmuch new --quiet
accounts = riseup, gmail
tells offlineimap that I have two accounts.
The password for each account, defined by remotepasseval
is mapped to two python functions, found in pythonfile = ~/.offlineimap_pass.py
. See below in the pass section for what the pythonfile
contains.
The rest I think is self explanatory, except for the name translations. This takes the IMAP name for certain default gmail, like [Google Mail].Starred
, and turns them into names that are easy to read in the Neomutt sidebar, like starred
. It’s also necessary to change these names back in the remote section of the config file.
Lastly, the postsynhook
calls notmuch to recompile it’s database, checking for new mail. I use notmuch
with alot
for searching emails sometimes. At some point it might be nice to see if I can run alot directly from mutt.
To get offlineimap to run every few minutes, to check if I have new mail, I use launchd, which is the successor to cron on macOS. I keep a script in ~/Library/LaunchAgents/
that looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin</string>
</dict>
<key>KeepAlive</key>
<false/>
<key>Label</key>
<string>homebrew.mxcl.offlineimap</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/opt/offlineimap/bin/offlineimap</string>
<string>-q</string>
<string>-u</string>
<string>basic</string>
</array>
<key>StartInterval</key>
<integer>300</integer>
<key>RunAtLoad</key>
<true />
<key>StandardErrorPath</key>
<string>/dev/null</string>
<key>StandardOutPath</key>
<string>/dev/null</string>
</dict>
</plist>
It runs offlineimap in quiet mode (-q
, -u basic
), every 300 seconds, and whenever the computer wakes from sleep or is powered on (RunAtLoad
), it also pipes error outputs to /dev/null
.
pass
This is the python file I use to call pass to get the passwords for each of my email accounts. Basically it just runs pass on the appropriate entry, then takes the output and grabs the appropriate line:
#!/usr/bin/env python2
from subprocess import check_output
def GetPassGmail():
return check_output("/usr/local/bin/pass email/gmail", shell=True).splitlines()[0]
def GetPassRiseup():
return check_output("/usr/local/bin/pass email/riseup", shell=True).splitlines()[1]
Mutt
Here is my Mutt config, which I keep in ~/.mutt/muttrc
:
# Source other files
source ~/.mutt/mutt_colours
source ~/.mutt/aliases
# Basic settings
set realname = "John Godlee"
set folder = "~/.mail"
# Text editor
# Use vim with minimal mail writing vimrc
set editor = "vim -u ~/.vimrc_alpine"
set charset = "utf-8"
unset record
# Ability to change headers manually in text editor
set edit_headers = yes
set autoedit = yes
# Pager View Options
set pager_index_lines = 0 # number of index lines to show
set pager_context = 3 # number of context lines to show
set pager_stop # don't go to next message automatically
set menu_scroll # scroll in menus
set tilde # show tildes like in vim
unset markers # no ugly plus signs
set mark_old = no # Don't add Old flags, just keep N
set mailcap_path = ~/.mutt/mailcap
auto_view text/html
# Status bar
set status_chars = " *%A"
set status_format = "───[Folder: %f]───[%r%m messages%?n? (%n new)?%?d? (%d to delete)?%?t? (%t tagged)?]───%>─%?p?( %p postponed )?───"
# Index
set date_format = "%b-%d"
set index_format = "[%Z] %X %M %-20.20F -- %s %* %[%Y_%m_%d] - %[%H:%M]"
set sort = threads # like gmail
set sort_aux = last-date-received # like gmail
set uncollapse_jump # don't collapse on an unread message
set sort_re # thread based on regex
set reply_regexp = "^(([Rr][Ee]?(\[[0-9]+\])?: *)?(\[[^]]+\] *)?)*"
# Pager
set pager_index_lines = 8 # number of index lines to show
set pager_context = 3 # number of context lines to show
set pager_stop # don't go to next message automatically
set menu_scroll # scroll in menus
set tilde # show tildes like in vim
unset markers # no ugly plus signs
alternative_order text/plain text/enriched text/html
# Sidebar
set sidebar_visible = yes
set sidebar_format = "%B %* [%N]%S"
## Don't abbreviate folders in sidebar
set sidebar_short_path
set sidebar_delim_chars = "/"
# Gmail mailboxes in sidebar
mailboxes "+--- gmail --------"
mailboxes +gmail/INBOX # Always have inbox at top of list
mailboxes `find ~/.mail/* -maxdepth 1 -mindepth 1 | sort | cut -d/ -f5- | sed 's/^/+/' | sed '/notmuch/d' | sed '/INBOX/d' | sed '/riseup/d' | tr '\n' ' '`
# Riseup mailboxes in sidebar
mailboxes "+--- riseup --------"
mailboxes +riseup/INBOX
mailboxes `find ~/.mail/* -maxdepth 1 -mindepth 1 | sort | cut -d/ -f5- | sed 's/^/+/' | sed '/INBOX/d' | sed '/gmail/d' | tr '\n' ' '`
# Multiple account setup
# Default to gmail setup
source ~/.mutt/accounts/gmail
# Folder hooks
folder-hook gmail/* source ~/.mutt/accounts/gmail
folder-hook riseup/* source ~/.mutt/accounts/riseup
# Macros for switching accounts - sources a separate config when a folder is opened
macro index } '<sync-mailbox><enter-command>source ~/.mutt/accounts/gmail<enter><change-folder>!<enter>'
macro index { '<sync-mailbox><enter-command>source ~/.mutt/accounts/riseup<enter><change-folder>!<enter>'
# Keybindings
bind index,pager R group-reply
bind index,pager r reply
bind index <space> collapse-thread
macro index C "<copy-message>?<toggle-mailboxes>" "copy a message to a mailbox"
macro index M "<save-message>?<toggle-mailboxes>" "move a message to a mailbox"
## Disable arrow keys
bind generic,pager,editor,index <Up> noop
bind generic,pager,editor,index <Down> noop
bind generic,pager,editor,index <Left> noop
bind generic,pager,editor,index <Right> noop
## Tagging and manipulating basics
bind index,pager c mail # compose
bind generic x tag-entry # Select Conversation
bind index x tag-thread # Select Conversation
bind pager x tag-message # Select Conversation
bind index,pager * flag-message # Star a message
bind index,pager a group-reply # Reply all
bind index,pager \# delete-thread # Delete
bind index,pager l copy-message # Label
bind index v save-message # Move to
## Sidebar interaction
bind index,pager B sidebar-toggle-visible
bind index,pager > sidebar-next
bind index,pager < sidebar-prev
bind index,pager ? sidebar-open
## Movement
bind pager i noop
bind index G last-entry
bind index j next-entry
bind index k previous-entry
bind pager k previous-line
bind pager j next-line
bind pager J next-entry
bind pager K previous-entry
bind pager q exit
bind pager v view-attachments
## Fast movement
bind editor <space> noop
bind index,pager g noop
macro index,pager gi "<change-folder>=gmail/INBOX<enter>" "Go to inbox"
bind index,pager h help
# Mail handling
set move = no
# Always include original message in reply and always reply to sender
set include = yes
set fast_reply
# Ask if unsent message should be kept as postponed
set postpone = ask-yes
It’s mostly a mash up of things I found online, but these are the bits I think are interesting.
I use a set of sed manipulations to get the names of mailboxes in both my gmail and riseup directories to fill the sidebar, using:
mailboxes `find ~/.mail/* -maxdepth 1 -mindepth 1 | sort | cut -d/ -f5- | sed 's/^/+/' | sed '/notmuch/d' | sed '/INBOX/d' | sed '/riseup/d' | tr '\n' ' '`
I use folder hooks and macros to load separate configs which change settings for replying to emails depending on which account I want to use. The extra configs look like this:
set from = "<EMAIL ADDRESS>"
set spoolfile = gmail/INBOX
set postponed = +gmail/drafts
set record = +gmail/sent
set sendmail = "/usr/local/bin/msmtp -a gmail"
macro index D \
"<delete-message><enter>" \
"Delete message permanently"
See that set sendmail
uses msmtp
to send the email, and uses the account called gmail. This is what my .msmtprc
looks like, which defines those accounts:
account riseup
host mail.riseup.net
port 587
protocol smtp
auth on
tls on
tls_trust_file /usr/local/etc/openssl/cert.pem
from <EMAIL ADDRESS>
user <EMAIL ADDRESS>
passwordeval "gpg --quiet --for-your-eyes-only --no-tty --decrypt ~/.password-store/email/riseup.gpg | sed -n 2p"
account gmail
host smtp.gmail.com
port 587
protocol smtp
auth on
tls on
tls_trust_file /usr/local/etc/openssl/cert.pem
from <EMAIL ADDRESS>
user <EMAIL ADDRESS>
passwordeval "gpg --quiet --for-your-eyes-only --no-tty --decrypt ~/.password-store/email/gmail.gpg"
account default : gmail
The last thing is the mailcap, which uses w3m to parse HTML email as plain text, and is called into mutt using set mailcap_path
:
text/html; w3m -dump -o document_charset=%{charset} '%s'; nametemplate=%s.html; copiousoutput