Introduction

GPG is only useful for the emails you actually remember to protect.

In mu4e, signing and encrypting a message is a manual gesture. Before sending, I hit C-c C-m s p to sign, or C-c C-m c p to sign and encrypt. It works, but it relies on me thinking about it every single time.

I don’t. Nobody does.

So in practice, the manual approach means most messages go out unsigned, and encryption only happens on the rare occasion I stop to remember it exists.

I wanted the opposite default. Something with no friction, that follows a clear policy:

  • Sign every message I send. Always.
  • Encrypt a message when, and only when, I have a public key for every recipient.

The second rule matters. Encryption is all-or-nothing: if a single recipient is missing from my keyring, encrypting would lock them out of their own email. So encryption has to be opportunistic — on when it can be, silently off when it can’t.

Here is how I wired that into mu4e.

The Building Blocks

Three pieces do the work.

MML secure tags. When you compose a message, Emacs doesn’t encrypt anything itself. It inserts an MML tag that tells the message layer what to do at send time. Two functions add those tags for me: mml-secure-message-sign and mml-secure-message-sign-encrypt.

message-send-hook. This hook runs right before a message is sent, when the headers are filled in and the recipients are known. That’s the perfect moment to decide whether to sign or to sign and encrypt.

epg. The Emacs interface to GnuPG. I use it to ask a simple question: do I have a public key for this address?

Two settings configure the signing context:

(setq mml-secure-openpgp-sign-with-sender t
      mml-secure-openpgp-encrypt-to-self t)

sign-with-sender tells the OpenPGP layer to pick the signing key based on the From address, which is exactly what I want when juggling several accounts.

encrypt-to-self is the one that saves you from a classic mistake. When you encrypt a message to someone else’s key, you can no longer read it — not even in your own Sent folder. Encrypting to yourself as well keeps your sent copies readable.

Collecting the Recipients

To decide whether encryption is possible, I first need the full list of people the message is going to.

(defun bounga/message-recipients ()
  "Return a list of all recipients in the message, looking at TO, CC and BCC.
Each recipient is in the format of `mail-extract-address-components'."
  (mapcan (lambda (header)
            (let ((header-value (message-fetch-field header)))
              (and
               header-value
               (mail-extract-address-components header-value t))))
          '("To" "Cc" "Bcc")))

The bounga/ prefix is my personal namespace. Emacs Lisp has a single global namespace, so prefixing your own functions is the convention that keeps them from colliding with built-ins or package code — never define a bare message-recipients that looks like it belongs to message.el. Pick whatever prefix you like; just be consistent.

I walk the To, Cc and Bcc headers, read each one with message-fetch-field, and parse it with mail-extract-address-components. The trailing t asks for all addresses in a header, not just the first, so a To line with five people returns five entries.

Bcc is in the list on purpose. A blind-carbon recipient still has to be able to read the message — if I can’t encrypt to them, I can’t encrypt at all.

mapcan concatenates the per-header results into a single flat list.

Do I Have Everyone’s Key?

Now the actual question: is there a public key in my keyring for every recipient?

(defun bounga/message-all-epg-keys-available-p ()
  "Return non-nil if the pgp keyring has a public key for each recipient."
  (require 'epa)
  (let ((context (epg-make-context epa-protocol)))
    (catch 'break
      (dolist (recipient (bounga/message-recipients))
        (let ((recipient-email (cadr recipient)))
          (when (and recipient-email (not (epg-list-keys context recipient-email)))
            (throw 'break nil))))
      t)))

I create an epg context, then loop over the recipients. For each one I ask epg-list-keys whether the keyring holds a key for that address. The email is the second element of each parsed recipient, hence cadr.

The logic is deliberately strict. The moment I find a single recipient with no key, I throw out of the loop and return nil: encryption is off the table. Only if the loop completes — every recipient covered — does the function return t.

The catch/throw pair is just an early exit. There is no point checking the remaining addresses once one is missing.

Tying It to Send

The decision function is tiny, because the two helpers did the hard part:

(defun bounga/message-sign-encrypt-if-all-keys-available ()
  "Add MML tag to encrypt message when there is a key for each recipient,
sign it otherwise.
Consider adding this function to `message-send-hook' to
systematically send signed / encrypted emails when possible."
  (if (bounga/message-all-epg-keys-available-p)
      (mml-secure-message-sign-encrypt)
    (mml-secure-message-sign)))

If I have everyone’s key, sign and encrypt. Otherwise, just sign. Either way, the message is signed — that part is never optional.

The last step is to run this before every send:

(add-hook 'message-send-hook 'bounga/message-sign-encrypt-if-all-keys-available)

message-send-hook fires once the message is complete and about to leave. By then the recipients are settled, so the keyring check reflects exactly who will receive the message.

The Result

With those few lines in place, GPG stops being something I have to remember.

Every email I send is signed. Whenever I happen to have public keys for all the recipients — which, over time, is more and more of my correspondence — the message is encrypted too, without a single keystroke on my part.

A couple of honest caveats.

Signing everything means every recipient can tell I use GPG, and gets a signature attachment whether they care about it or not. For me that’s a feature, not a cost.

And the policy is only as good as my keyring. Encryption depends on having imported the right public keys; nothing here fetches them for me. When a key is missing, the message silently falls back to signing only — which is the safe failure mode, but a silent one. If you’d rather be told, this is the spot to add a notice.

None of that changes the day-to-day feel, though: I write email, I send it, and the right cryptography happens on its own.

BTW, my public PGP key is available here: DB19B66E.

Have comments or want to discuss this topic?

Send an email to ~bounga/public-inbox@lists.sr.ht