Configuring e-mail with mu4e in Emacs

These are my notes for setting up e-mail client in Emacs (mu4e) with isync and msmtp. I have two mail accounts that I want to use in mu4e and one of them is gmail account. The other one is from self-hosted mail service. I should mention that I’m using Doom Emacs. This post also contains additional information on my sway + ly keyring configuration.

Here is list of all the dependencies needed:

IMAP setup

For IMAP I use mbsync (isync) to synchronize my maildirs. This configuration is pretty straight forward, there are only some specificities for gmail. ~/.config/isyncrc

IMAPAccount gmail
Host imap.gmail.com
User user@gmail.com
PassCmd "oama access user@gmail.com"
AuthMechs XOAUTH2
TLSType IMAPS
CertificateFile /etc/ssl/certs/ca-certificates.crt

IMAPStore gmail-remote
Account gmail

MaildirStore gmail-local
Subfolders Verbatim
Path ~/.mail/Gmail/
Inbox ~/.mail/Gmail/Inbox

Channel gmail
Far :gmail-remote:
Near :gmail-local:
Patterns * ![Gmail]* "[Gmail]/Sent Mail" "[Gmail]/Starred" "[Gmail]/All Mail" "[Gmail]/Bin"
Create Both
Expunge Both
SyncState *

IMAPAccount soukev
Host mail.soukev.xyz
Port 143
User soukev@soukev.xyz
PassCmd "secret-tool lookup foo bar"
TLSType STARTTLS
TLSVersions +1.3
CertificateFile /etc/ssl/certs/ca-certificates.crt
AuthMechs LOGIN

IMAPStore soukev-remote
Account soukev

MaildirStore soukev-local
Path ~/.mail/Soukev/
Inbox ~/.mail/Soukev/INBOX/
Trash ~/.mail/Soukev/Trash/
SubFolders Verbatim

Channel soukev
Far :soukev-remote:
Near :soukev-local:
Patterns *
Create Both
SyncState *

For gmail it’s important to include Expunge Both and set all Patterns correctly. Trash folder name may vary e.g. [Gmail]/Bin, [Gmail]/Trash.

We also need to set up two more things for this configuration to work oama and gnome-keyring. Oama is there, because gmail requires oauth. And I use gnome-keyring for oama to store gmail token and also for my own password to the other mail account.

Keyring

Here I use gnome-keyring and secret-tool. I don’t use any specific configuration for these programs. Only thing that I have configured is automatic start and unlocking of the keyring.

In /etc/pam.d/ly I added following:

auth       optional     pam_gnome_keyring.so
session    optional     pam_gnome_keyring.so auto_start

This allows for passing login password to gnome keyring.

And in my sway configuration I have:

exec --no-startup-id /usr/bin/gnome-keyring-daemon --start --components=secrets,ssh,gpg,pkcs11

To store password for non-oauth account we can then do:

$ secret-tool store --label='foo-bar' foo bar

Oama setup

For oama to properly works we need to first get client id and secret for oauth. To get this we need to create google project and oauth client which should provide us with necessary credentials. I’m sorry, but I’m not going to provide you with exact steps to set this up in google cloud because they tend to change how it works, and I refuse to update this guide to keep it up-to-date.

When we get our id and secret we can configure oama.

~/.config/oama/config.yaml

encryption:
    tag: KEYRING
services:
  google:
    client_id: CLIENT-ID
    client_secret: CLIENT-SECRET

Setting encryption - tag to keyring means it tries to use gnome keyring, alternatively GNU PG, KDE Wallet or KeePassXC can be used.

Now we need to run the following for the first time to authorize our device:

$ oama authorize google user@gmail.com

google rant

First, fuck oauth. It’s just weaponized security.

Google refuses to provide standard way of moving mail to maildirs (including deletion). Instead, it uses labels. So by default when we move mail to different maildir, it only adds new label when synced with IMAP. This is why I have Expunge Both in mbsync config, which seems to solve this issue.

Gmail also has weirdly specific way of handling mail trashing and deletion. The trash maildir name may vary based on your account localization (e.g. British English = Bin, American English = Trash). Great job, google!

In other mail services deleting mail through IMAP means completely removing, if we want to just trash it, we move it to trash maildir. Pretty simple.

In google they didn’t like it. Deleting mail removes all labels and leaves it ’archived’ in ’All mail’ by default. Fortunately we can change gmail settings to fix this, but I’m guessing most people are unaware of this. Before using mu4e I was thunderbird user and I didn’t know that my mail wasn’t deleted at all.

It seems the only reason that google does this, is to clutter user’s email box, if they don’t use google app, so that they’re incentivized to buy more space. They just want to do everything in their power to force users to use their apps so they could be tracked.

The only reason google provides their APIs and IMAP, SMTP is to have deniability if accused of monopolization.

“Oh noo, they don’t have to use gmail web app, they can use other mail clients for suuuurreee…” – Probably some google lawyer.

Additional Gmail setup

To make gmail behave as we want to, we need to change one important setting in the gmail web client. In gmail go to Settings -> ’See all settings’ -> ’Forwarding and POP/IMAP’, set ’Auto-Expunge off’ and then ’Move the message to the Bin’. This should ensure that mails are correctly moved to trash maildir on removal.

Finalize IMAP setup

Now we should be able to sync our IMAP maildirs and index them with mu:

$ mbsync -a
$ mu init --maildir=~/.mail --my-address=user@gmail.com --my-address=soukev@soukev.xyz
$ mu index

SMTP configuration

For SMTP configuration we also need to use oama and secret-tool for authentication.

~/.config/.msmtprc

# Set default values for all following accounts.
defaults
auth           on
tls            on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
logfile        ~/.msmtp.log

# Gmail
account Gmail
from user@gmail.com
user user@gmail.com
auth oauthbearer
passwordeval oama access user@gmail.com
host smtp.gmail.com
port 587

# Soukev
account Soukev
from soukev@soukev.xyz
user soukev@soukev.xyz
passwordeval secret-tool lookup foo bar
host mail.soukev.xyz
port 587

mu4e configuration

This is straight forward now, just make sure trash folder is correctly set for gmail.

;; Base mail setup
(setq mu4e-get-mail-command "mbsync -a"
      mu4e-update-interval 900
      mu4e-trash-without-flag t
      mu4e-change-filenames-when-moving t
      mu4e-context-policy 'ask
      mu4e-compose-context-policy 'ask)
     
(after! mu4e
  (setq sendmail-program (executable-find "msmtp")
        send-mail-function #'smtpmail-send-it
        message-sendmail-f-is-evil t
        message-sendmail-extra-arguments '("--read-envelope-from")
        message-send-mail-function #'message-send-mail-with-sendmail))
       
(set-email-account! "Gmail"
  '((user-mail-address . "user@gmail.com")
    (user-full-name    . "user")
    (mu4e-sent-folder  . "/Gmail/[Gmail]/Sent Mail")
    (mu4e-drafts-folder . "/Gmail/[Gmail]/Drafts")
    (mu4e-trash-folder . "/Gmail/[Gmail]/Bin")
    (mu4e-refile-folder . "/Gmail/[Gmail]/All Mail")
    (mu4e-maildir-shortcuts .
      (("/Gmail/Inbox"             . ?i)
       ("/Gmail/[Gmail]/Sent Mail" . ?s)
       ("/Gmail/[Gmail]/Bin"     . ?t)
       ("/Gmail/[Gmail]/All Mail"  . ?a))))
  t)  ;; t = make this the default account
 
(set-email-account! "Soukev"
  '((user-mail-address . "soukev@soukev.xyz")
    (user-full-name    . "Soukev")
    (mu4e-sent-folder  . "/Soukev/Sent")
    (mu4e-drafts-folder . "/Soukev/Drafts")
    (mu4e-trash-folder . "/Soukev/Trash")
    (mu4e-refile-folder . "/Soukev/Archive")
    (mu4e-maildir-shortcuts .
      (("/Soukev/INBOX" . ?i)
       ("/Soukev/Sent"  . ?s)
       ("/Soukev/Trash" . ?t)))))
Published: 2025-06-15
Comments? Let me know.