Setting up Emacs with Mu4e, Protonmail, and Gmail
Overview
Here's how to configure MU4e to work with Protonmail and Gmail simultaneously
ProtonMail Bridge
Overview:
- Create a passwordless GPG key
- Compile the bridge
- Login and export bridge certificates
- Start the bridge as a service
Password-less GPG
I don't have a keyring system installed. Protonmail will fail to log into your account unless you have a passwordless GPG key created. I couldn't get it to work with pinentry-tty.
sudo apt install gpg pass
# Creating the GPG keypair
gpg --batch --passphrase '' --quick-gen-key [email protected] default default
# Listing the public key ID
gpg --list-keys
# Using pass to initialize a password store for my public key
pass init <GPGID>
Compile Bridge
Install GoLang version 1.22 or later. This is required for the bridge. Add the export variables to your shell startup script.
wget https://go.dev/dl/go1.22.1.linux-amd64.tar.gz
sudo tar -C /usr/local -xzvf go1.22.1.linux-amd64.tar.gz
export GOROOT="/usr/local/go"
export GOHOME="$HOME/.go"
export PATH="/usr/local/go/bin:$PATH"
Compile the bridge without GUI support as seen below. I prefer to place my software in /opt. Its not necessary.
sudo apt install build-essenital libsecret-1-dev
git clone https://github.com/ProtonMail/proton-bridge
cd proton-bridge
make build-nogui
sudo chown $USER:$USER /opt -R
cd ../
mv proton-bridge /opt
/opt/proton-bridge/bridge --cli
Login and export certificates
Run the bridge using the interactive CLI. Follow the commands to log in.
When running cert export
, place the certificate file in ~/.config/protonmail/bridge/{cert,key}.pem
cd /opt/proton-bridge/
./bridge --cli
# Signing into the account
login
# Previewing and saving the password
info
# Disabling telemetry
telemetry disable
# Exporting the certificate
cert export
Systemd Service
Create the following service file
/etc/systemd/system/proton-bridge.service
[Unit]
Description=ProtonMail Bridge
After=network.target
[Service]
Type=simple
ExecStart=/opt/proton-bridge/bridge -n
User=USERNAME
Group=USERNAME
Restart=always
[Install]
WantedBy=multi-user.target
Start the servive
systemctl enable --now proton-bridge
Compiling Mu4e
Emacs is required as a dependency for the installation. Either install it with
apt
or add it to your $PATH
sudo apt-get install -y git meson libgmime-3.0-dev libxapian-dev gnutls-bin texinfo libcld2-dev cmake guile-3.0
git clone https://github.com/djcb/mu.git
cd mu
./autogen.sh
make
sudo make install
Downloading Emails
Overview:
- Encrypt passwords using
pass
- Install Isync, create config, & sync email
- Index maildir
Encrypting passwords
I retrieved the protonmail password from the bridge by running info
. The Gmail
account is using an APP Password. To create an app password for Gmail, enable
2FA on your gmail account then google the direct link to the app password page.
In the future, ISync will have to be replaced with XOATH2.0 and offline IMAP to work. Embrace the luxury while it lasts.
pass insert "email/[email protected]"
pass insert "email/[email protected]"
Installing & Configuring ISync
Create the folders that will store your email
mkdir -p ~/Mail/[email protected] ~/Mail/[email protected]
Copy the configuration file below. Update the lines for the email address and password commands.
sudo apt install openssl isync
vim .mbsyncrc
IMAPStore gmail-remote
Host imap.gmail.com
SSLType IMAPS
AuthMechs LOGIN
User [email protected]
PassCMD "pass email/[email protected]"
MaildirStore gmail-local
Path ~/Mail/[email protected]/
Inbox ~/Mail/[email protected]/INBOX
Subfolders Verbatim
Channel gmail
Master :gmail-remote:
Slave :gmail-local:
Create Both
Expunge Both
Patterns * !"[Gmail]/All Mail" !"[Gmail]/Important" !"[Gmail]/Starred" !"[Gmail]/Bin"
SyncState *
IMAPAccount protonmail
Host 127.0.0.1
Port 1143
User [email protected]
PassCMD "pass email/[email protected]"
SSLType STARTTLS
SSLVersions TLSv1.2
CertificateFile ~/.config/protonmail/bridge/cert.pem
IMAPStore pm-remote
Account protonmail
MaildirStore pm-local
Path ~/Mail/[email protected]/
Inbox ~/Mail/[email protected]/INBOX/
Channel pm-inbox
Master :pm-remote:
Slave :pm-local:
Patterns "INBOX"
Create Both
Expunge Both
SyncState *
Channel pm-sent
Master :pm-remote:"Sent"
Slave :pm-local:"sent"
Create Both
Expunge Both
SyncState *
Channel pm-trash
Master :pm-remote:"Trash"
Slave :pm-local:"trash"
Create Both
Expunge Both
SyncState *
Channel pm-spam
Master :pm-remote:"Spam"
Slave :pm-local:"spam"
Create Both
Expunge Both
SyncState *
Group protonmail
Channel pm-inbox
Channel pm-sent
Channel pm-trash
Channel pm-spam
Download all the emails. Gmail's limit is 2500MB of emails per day.
mbsync -a
Indexing the maildir
Now initialize the folder with mu and index it.
mu init --maildir=~/Mail \
[email protected] \
[email protected]
mu index
Configuring Mu4e
Overview:
- Create encrypted .authinfo file
- Copy my MU4E config
- Run Mu4e
Creating encrypted authinfo file
The authinfo file stores the SMTP credentials for sending emails. Add this line to your init.el file
(require 'auth-source)
(setq auth-sources '("~/.authinfo.gpg"))
Set the contents of the .authinfo.gpg file to the following. Emacs will automatically encrypt it using your GPG key.
machine 127.0.0.1 login [email protected] port 1025 password PASSWORDHERE
machine smtp.gmail.com login [email protected] port 587 password PASSWORDHERE
Copy Mu4E config
This won't be a tutorial for how to use Mu4e. The important thing to note is this: Use the semicolon key to switch contexts between the personal and work accounts. This will update the bookmark keys to jump between inboxes. When composing emails you can select the context to send from. W3M is required for rendering HTML documents. Fonts are required for displaying Emoji's.
(require 'mu4e)
; Default folder containing email
(setq mu4e-maildir "~/Mail")
;; Don't keep message buffers around
(setq message-kill-buffer-on-exit t)
; Avoid keeping self in CC
(setq mu4e-compose-keep-self-cc nil)
; Function to send mail (via SMTP)
(setq send-mail-function 'smtpmail-send-it)
; Specify the type of SMTP connections to use
(setq smtpmail-stream-type 'starttls)
; Command to convert HTML emails to plain text
(setq mu4e-html2text-command "w3m -T text/html")
; Interval to automatically update email (in seconds)
(setq mu4e-update-interval 60)
; Automatically update headers
(setq mu4e-headers-auto-update t)
; Enable inline images in emails
(setq mu4e-view-show-images t)
; Disable automatic inclusion of signatures in new emails
(setq mu4e-compose-signature-auto-include nil)
; Use fancy characters in the interface
(setq mu4e-use-fancy-chars t)
; Set mu4e as the default email agent
(setq mail-user-agent 'mu4e-user-agent)
; Use Ivy for completing read prompts
(setq mu4e-completing-read-function 'ivy-completing-read
mu4e-confirm-quit nil)
; Disable threading in the headers view (can toggle with "P")
(setq mu4e-headers-show-threads nil)
; Enable visual-line-mode in the email view mode
(add-hook 'mu4e-view-mode-hook #'visual-line-mode)
; Show threads
(setq mu4e-headers-show-threads t)
; Custom header emojis
(setq
mu4e-headers-draft-mark '("" . "💈")
mu4e-headers-flagged-mark '("" . "📍")
mu4e-headers-new-mark '("" . "🔥")
mu4e-headers-passed-mark '("" . "❯")
mu4e-headers-replied-mark '("" . "❮")
mu4e-headers-seen-mark '("" . "☑")
mu4e-headers-trashed-mark '("" . "💀")
mu4e-headers-attach-mark '("" . "📎")
mu4e-headers-encrypted-mark '("" . "🔒")
mu4e-headers-signed-mark '("" . "🔑")
mu4e-headers-unread-mark '("" . "🔥")
mu4e-headers-calendar-mark '("" . "📅"))
; Prompt to reply all
(defun compose-reply-wide-or-not-please-ask ()
"Ask whether to reply-to-all or not."
(interactive)
(mu4e-compose-reply (yes-or-no-p "Reply to all?")))
(define-key mu4e-compose-minor-mode-map (kbd "R")
#'compose-reply-wide-or-not-please-ask)
(define-key mu4e-headers-mode-map (kbd "R") 'compose-reply-wide-or-not-please-ask)
(define-key mu4e-view-mode-map (kbd "R") 'compose-reply-wide-or-not-please-ask)
; Email sync command
(setq mu4e-get-mail-command "mbsync -a"
mu4e-change-filenames-when-moving t ; needed for mbsync
mu4e-update-interval 120) ; update every 2 minutes
; Function to add CC and BCC headers automatically
(defun my-add-header ()
"Add CC: and Bcc: header"
(save-excursion (message-add-header
(concat "CC: " "\n")
;; pre hook above changes user-mail-address.
(concat "Bcc: " "\n"))))
(add-hook 'mu4e-compose-mode-hook 'my-add-header)
; Define email contexts
(setq mu4e-contexts
(list
;; Work account
(make-mu4e-context
:name "Work"
:match-func
(lambda (msg)
(when msg
(string-prefix-p "/[email protected]" (mu4e-message-field msg :maildir))))
:vars '((user-mail-address . "[email protected]")
(user-full-name . "First Last")
(mu4e-drafts-folder . "/[email protected]/[Gmail]/Drafts")
(mu4e-sent-folder . "/[email protected]/[Gmail]/Sent Mail")
(mu4e-refile-folder . "/[email protected]/[Gmail]/All Mail")
(mu4e-trash-folder . "/[email protected]/[Gmail]/Trash")
(smtpmail-smtp-server . "smtp.gmail.com") ; host running SMTP server
(smtpmail-smtp-service . 587) ; SMTP service port number
;(mu4e-sent-messages-behavior . 'delete) ; Needed for gmail accounts
(mu4e-compose-reply-ignore-address . '("no-?reply" "[email protected]"))
(mu4e-maildir-shortcuts . (("/[email protected]/INBOX" . ?i)
("/[email protected]/[Gmail]/Sent Mail" . ?s)
("/[email protected]/[Gmail]/Trash" . ?t)
("/[email protected]/[Gmail]/Spam" . ?j)
("/[email protected]/[Gmail]/Drafts" . ?d)
))
))
;; Personal account settings
(make-mu4e-context
:name "Personal"
:match-func
(lambda (msg)
(when msg
(string-prefix-p "/[email protected]" (mu4e-message-field msg :maildir))))
:vars '((user-mail-address . "[email protected]")
(user-full-name . "First Last")
(mu4e-drafts-folder . "/[email protected]/drafts")
(mu4e-sent-folder . "/[email protected]/sent")
(mu4e-refile-folder . "/[email protected]/archive")
(mu4e-trash-folder . "/[email protected]/trash")
(mu4e-html2text-command . "w3m -T text/html")
;; SMTP settings:
(smtpmail-smtp-server . "127.0.0.1") ; host running SMTP server
(smtpmail-smtp-service . 1025) ; SMTP service port number
(mu4e-compose-reply-ignore-address . '("no-?reply" "[email protected]"))
(mu4e-maildir-shortcuts . (("/[email protected]/INBOX" . ?i)
("/[email protected]/Archive" . ?a)
("/[email protected]/drafts" . ?d)
("/[email protected]/sent" . ?s)
("/[email protected]/trash" . ?t)
("/[email protected]/spam" . ?j)
))
))))
; Bookmarks for quick email search
(setq mu4e-bookmarks
'((:name "Unread messages"
:query "flag:unread and maildir:/Inbox"
:key ?u)
(:name "Today's messages"
:query "date:today..now"
:key ?t)
(:name "Last 7 days"
:query "date:7d..now"
:key ?7)
(:name "Messages with PDF"
:query "mime:application/pdf"
:key ?p)
(:name "Messages with images"
:query "mime:image/*"
:key ?i)
(:name "Messages with calendar event"
:query "mime:text/calendar"
:key ?e)
(:name "Messages with Word docs"
:query "mime:application/msword OR mime:application/vnd.openxmlformats-officedocument.wordprocessingml.document"
:key ?w)
))
It should look like this by the end. I'm using org-msg to compose HTML documents
using org mode. Additionally I used the mu4e-column-faces
plugin to have more colors. The colorscheme is modus-operandi-tinted.