Protonmail is a great service, after using it for over a year I cannot see myself returning to GMail or moving to another email service in the near future. Out of the box however it misses a few key features:

  • Offline backups
  • Desktop client compatibility
  • The ability to search the message body (by design I know, but still frustrating)

With a little tinkering these are all solvable problems, and using CLI tools (mostly).

Protonmail Bridge

Out of the gate we need to be able to connect to Protonmail using standard protocols, like IMAP, which are not available by default. To achieve this we need a paid Protonmail account so we have access to Protonmail bridge. You shouldn’t need a whole lot of guidance to get this up and running, but the official documentation can be found here.

Once the bridge is up and running you will have IMAP and SMTP services running locally with the bridge translating those two protocols to Protonmail’s API.

Why not hydroxide

Hydroxide is a third party alternative to the official bridge. I tried it out but it has issues, most notably the inability to move emails between folders, so I stuck to Bridge.

Offlineimap

Offlineimap is going to be synchronizing our mailbox to a local directory, fulfilling our requirement for an offline backup as well as making our mail available to our local client. Install it using your package manager (brew for me) brew install offlineimap and create a configuration file in your home directory called .offlineimaprc, using this template:

# ~/.offlineimaprc
[general]
accounts = main
pythonfile = ~/.offlineimap.py

[Account main]
localrepository = main-local
remoterepository = main-remote

# full refresh, in min
autorefresh = 0.2

# quick refreshs between each full refresh
quick = 10

# update notmuch index after sync
postsynchook = notmuch new

[Repository main-local]
type = Maildir
localfolders = ~/.mail

# delete remote mails that were deleted locally
sync_deletes = yes

[Repository main-remote]
type = IMAP
remoteport = 1143
remotehost = 127.0.0.1
remoteuser = $YOUR_EMAIL@protonmail.com
# remotepass = $SOME_PASS
remotepasseval = get_keychain_pass(account="$YOUR_EMAIL@protonmail.com", server="imaps://localhost")
keepalive = 60
holdconnectionopen = yes

# delete local mails that were deleted on the remote server
expunge = yes

# sync only these folders
folderfilter = lambda foldername: foldername in ['INBOX', 'Archive', 'Sent', 'Trash']

# SSL
ssl = no
starttls = yes
sslcacertfile = ~/.protonbridge.pem

Most of this file is pretty boring but there are some pieces to be aware of:

Credential Management

Skip this by uncommenting remotepass and entering the bridge password, but know that means you are storing a credential in plain text and that comes with all the risks you think it does and more. Don’t forget to comment out pythonfile and remotepasseval if you do this

My solution to this is MacOS focused, if you need a solution for another OS I would check out gpg. We do not want to store credentials in plain text, lets use the Keychain to store the password instead. Open up Keychain Access, select the “login” chain and select File -> New Password Item:

Keychain Item Name = imaps://localhost
Account Name = $YOUR_EMAIL@protonmail.com
Password = $BRIDGE_PASSWORD

We will use this entry in various ways in our configuration, but for offlineimap we need to craft a little python to get the password. Create .offlineimap.py in your home directory and copy in the following code

# ~/.offlineimap.py

import re, commands
def get_keychain_pass(account=None, server=None):
    params = {
        'security': '/usr/bin/security',
        'command':  'find-generic-password',
        'account':  account,
        'server':   server
    }

    command = "%(security)s %(command)s -a %(account)s -s %(server)s -w" % params
    outtext = commands.getoutput(command)
    return outtext

This is a modified version of a python script found here

This python script is imported at pythonfile and the function is used by remotepasseval to get our password from the Keychain when it’s needed.

Certificates and TLS

We need to provide a certificate file for this to work correctly due to Proton Bridge using a self signed cert. Once the bridge is running we can get the certificate by running openssl s_client -starttls imap -connect 127.0.0.1:1143 -showcerts. Copy -----BEGIN CERTIFICATE----- through to ----END CERTIFICATE----- and paste it into a new file in your home directory called .protonbridge.pem.

Notmuch

Notmuch is new to my setup so I do not have much to say about it at this time (ha). I will write more about it later but for now we will use a simple setup. Install with your package manager of choice, for example brew install notmuch and run notmuch setup to get a basic config going.

Mutt (or Neomutt)

I use Neomutt instead of Mutt so all of the configurations assume Neomutt’s directory structure, they should be pretty similar though. Install Neomutt using your trusty package manager, brew install neomutt perhaps. Next we create a configuration file for Neomutt at ~/.config/neomutt/neomuttrc and copy in the following:

## Accounts
# Variables
set my_user = "$YOUR_EMAIL@protonmail.com"
set my_pass = `security find-generic-password -a YOUR_EMAIL@protonmail.com -s imaps://localhost -w`

# Protonmail

set realname = "Firstname Lastname"
set smtp_url = smtp://$my_user:$my_pass@127.0.0.1:1025
set ssl_force_tls
set ssl_starttls
set from = "$YOUR_EMAIL@protonmail.com"
set envelope_from
set use_from = "yes"

set mbox_type=Maildir
set folder=~/.mail/
set record=+Sent
set postponed=+Drafts
set trash=+Trash
set mail_check=2 # seconds

set virtual_spoolfile # use first defined virtual-mailbox as spool
virtual-mailboxes "INBOX" "notmuch://?query=folder:INBOX"
virtual-mailboxes "Archive" "notmuch://?query=folder:Archive"
virtual-mailboxes "Sent" "notmuch://?query=folder:Sent"
virtual-mailboxes "Trash" "notmuch://?query=folder:Trash"

This is a snippet from my neomuttrc that handles mail access and SMTP, I will publish my full configuration at some point, but this will get you started

Things to note here are set my_pass which is once again grabbing our password from the Keychain and the use of virtual-mailboxes to leverage notmuch, even if we are not doing much with it in this simple configuration.

Startup

Now we have everything in place we can get it running. Make sure Proton Bridge is running then execute offlineimap in a terminal to start the synchronization process. Once it is done offlineimap will call notmuch to index the emails thanks to postsynchook = notmuch new in its configuration file. After the initial sync is done (which may take a while) you can launch neomutt and start working on your email!

Assuming everything works you probably want Proton Bridge and offlineimap to run on startup, which I will leave up to you and your OS.