Setting up Emacs with Mu4e, Protonmail, and Gmail

7 min read January 11, 2025 1340 words

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.

https://paste.fe00.xyz/wtJy/Screenshot%20from%202025-01-12%2000-37-47.png

https://paste.fe00.xyz/26kc/Screenshot%20from%202025-01-12%2000-38-16.png