Setting Up Email in Emacs (and the Gotchas Nobody Warns You About)
I have wanted to read and send email from inside Emacs for a long time. Having everything - notes, agenda, code, and now email - in one place is the logical conclusion of the Emacs rabbit hole. This week I finally got it working properly, and it was not without its moments.
This post covers what I set up, what broke, and - most usefully - the non-obvious things I wish someone had written down.
The Stack
There are a few moving parts:
- mu4e - the Emacs email client. Reads a local maildir on disk.
- mbsync (isync) - syncs that maildir from Gmail over IMAP.
- GPG - encrypts your credentials so you are not storing an app password in plain text.
- pinentry - routes GPG passphrase prompts into the Emacs minibuffer rather than a GUI pop-up.
Install the system dependencies first:
sudo apt install mu isync
Credentials
Gmail requires an App Password rather than your main account password for third-party clients. Generate one at myaccount.google.com/apppasswords - you will need 2FA enabled.
Store it encrypted:
cat > /tmp/authinfo-plain <<'EOF'
machine imap.gmail.com login you@yourdomain.com password YOUR_APP_PASSWORD
machine smtp.gmail.com login you@yourdomain.com password YOUR_APP_PASSWORD
EOF
gpg --output ~/.authinfo.gpg --symmetric /tmp/authinfo-plain
rm /tmp/authinfo-plain
The credentials live in ~/.authinfo.gpg. Nothing sensitive goes in your config files.
mbsync
Create ~/.mbsyncrc:
IMAPAccount workspace
Host imap.gmail.com
Port 993
User you@yourdomain.com
PassCmd "gpg --quiet --for-your-eyes-only --no-tty --decrypt ~/.authinfo.gpg | awk '/machine imap.gmail.com login you@yourdomain.com/{print $NF}'"
SSLType IMAPS
CertificateFile /etc/ssl/certs/ca-certificates.crt
IMAPStore workspace-remote
Account workspace
MaildirStore workspace-local
SubFolders Verbatim
Path ~/Mail/
Inbox ~/Mail/Inbox
Channel workspace
Far :workspace-remote:
Near :workspace-local:
Patterns * ![Gmail]* "[Gmail]/Sent Mail" "[Gmail]/Trash" "[Gmail]/All Mail"
Create Both
Expunge Both
SyncState *
Initialise mu and do the first sync:
mu init --maildir=~/Mail --my-address=you@yourdomain.com
mu index
mbsync -a
Emacs Config
(use-package mu4e
:ensure nil
:load-path "/usr/share/emacs/site-lisp/mu4e"
:config
(setq mu4e-maildir "~/Mail"
mu4e-get-mail-command "mbsync -a"
mu4e-update-interval 300
mu4e-change-filenames-when-moving t
mu4e-sent-messages-behavior 'delete
send-mail-function 'smtpmail-send-it
message-send-mail-function 'smtpmail-send-it
smtpmail-smtp-server "smtp.gmail.com"
smtpmail-smtp-service 465
smtpmail-stream-type 'ssl)
(global-set-key (kbd "C-c M") 'mu4e))
For composing in org syntax (recipients get clean HTML), add org-msg:
(use-package org-msg
:ensure t
:after mu4e
:config
(setq org-msg-options "html-postamble:nil H:5 num:nil ^:{} toc:nil author:nil email:nil \\n:t"
org-msg-greeting-fmt "\nHi%s,\n\n"
org-msg-default-alternatives '((new . (text html))
(reply-to-html . (text html))
(reply-to-text . (text))))
(org-msg-mode))
Fixing the GPG Password Pop-Up
By default, GPG asks for your passphrase every time mbsync runs - every five minutes if you set mu4e-update-interval 300. That gets old quickly.
Two fixes, which work well together.
First, tell gpg-agent to cache the passphrase for 24 hours. Add to ~/.gnupg/gpg-agent.conf:
default-cache-ttl 86400
max-cache-ttl 604800
allow-emacs-pinentry
Then restart the agent:
gpgconf --kill gpg-agent
Second, install the pinentry package so prompts appear in the minibuffer rather than a GUI dialog:
(use-package pinentry
:ensure t
:config
(pinentry-start))
You enter the passphrase once after booting. After that: silence.
The Gmail Alias Problem
This is the one that cost me the most time.
I send from tim@timothyjohnsonsci.com, which is a “Send mail as” alias on my Google Workspace account. mu4e was configured correctly - the right From: header was going out - but recipients were seeing a different address. Inspecting the raw headers told the story:
From: Timothy Johnson <tim@timothyjohnsonenterprises.com>
X-Google-Original-From: Timothy Johnson <tim@timothyjohnsonsci.com>
Gmail was receiving the right From: header, logging it in X-Google-Original-From, and then rewriting it. This happens when Gmail can’t verify the From: address against its list of allowed aliases for your account.
I spent a while checking SMTP variables, adding smtpmail-mail-address, trying message-sendmail-envelope-from - none of it made a difference. The config was fine.
The actual cause: when I originally added the alias in Gmail settings, I had typed Tim@timothyjohnsonsci.com with a capital T. Gmail stores aliases exactly as entered and does a case-sensitive match when verifying outbound mail. My config used lowercase tim@, which didn’t match, so Gmail rewrote it.
The fix was to delete the alias in Gmail, re-add it in lowercase, go through verification again, and update the config to match. A one-character difference, and a good reminder to check the obvious things before diving into SMTP internals.
Multiple Contexts
If you send from more than one address, mu4e contexts let you switch automatically based on which address a message was sent to:
(setq mu4e-contexts
`(,(make-mu4e-context
:name "primary"
:match-func (lambda (msg)
(when msg
(mu4e-message-contact-field-matches
msg :to "you@yourdomain.com")))
:vars '((user-mail-address . "you@yourdomain.com")
(user-full-name . "Your Name")))
,(make-mu4e-context
:name "other"
:match-func (lambda (msg)
(when msg
(mu4e-message-contact-field-matches
msg :to "you@otherdomain.com")))
:vars '((user-mail-address . "you@otherdomain.com")
(user-full-name . "Your Name"))))
mu4e-context-policy 'pick-first
mu4e-compose-context-policy 'ask-if-none)
pick-first selects the first context when viewing mail. ask-if-none prompts you to choose when composing a new message that doesn’t match any context automatically.
Was It Worth It?
Yes. Having email alongside my org agenda, notes, and calendar in a single environment removes a surprising amount of friction. Capturing an email as a TODO with a link back to the thread (C-c c m) is genuinely useful, and composing in org-mode means I can write structured replies with headings and code blocks without thinking about formatting.
The setup took longer than it should have - mostly because of that alias capitalisation issue - but the result is stable and the config is small enough to understand completely.
If you hit the same Gmail alias rewrite problem, check your capitalisation before anything else.