Insert Cc and Bcc Mail Headers Conditionally in Emacs message-mode

In my Emacs email setup (notmuch.el using message-mode), I had this for the longest time:

(setq message-default-mail-headers "Cc: \nBcc: \n")

It would insert Cc and Bcc headers into email composition buffers so that I could quickly edit these fields.

However, the message-default-mail-headers insertion is done unconditionally, so when replying to mail with people receiving a carbon copy, I would end up with two such header lines:

To: ada@example.com
Cc: bob@example.com
Cc:
Bcc:

The empty Cc/Bcc lines usually don’t cause trouble, unless there’s already a Cc/Bcc line – then some mail servers reject the email. Spam protection or something. It was not supposed to be this way, and multiple Cc header lines were permitted:

This specification permits multiple occurrences of most fields. —RFC 822, Section 4.1, 1982

See?

But here we are, post-2001, when RFC 2822 deviates with min/max occurrences. I didn’t know! So how do we get there?

Looking at the variable’s origin in code, it says this variable is intended to ease migration from mail-mode. That alerted me to look just above that declaration and find message-default-headers, which accepts a function.

The message-mode manual on “Message Headers” doesn’t offer any more detail on that, but my experiments show that when message-default-headers is called, the mail composition buffer only contains From, To, Subject, and that’s it. No -- to separate header from content, no signature, yet. So I can check whether Cc/Bcc header lines exist:

(defun ct/message-insert-headers () 
  "Inserts CC/BCC headers, but only if they aren't already present."
  (let* ((headers (buffer-substring-no-properties (point-min) (point-max)))
         (additions (list
                     (unless (string-match "^Cc:" headers) "Cc: \n")
                     (unless (string-match "^Bcc:" headers) "Bcc: \n"))))
    (string-join additions)))
    
(setopt message-default-headers #'ct/message-insert-headers)

Well, “insert” is a lie, the function returns the headers, but for the intended use it’s fine with me.

I can perceive a brief flash of changes when composing emails: the buffer appears, then something happens and is inserted immediately. That didn’t happen before – but the call site looks like it would run this (new to me) function before inserting the (previously used) string message-default-mail-headers. So that’s a mystery I can live with.

During all of this, I noticed that message.el is nested in the gnus/ directory. I didn’t realize that this originated in the Gnus newsreader project.