I’ve used Emacs as my mail client since the 1990s—first with Gnus, now with Notmuch. One of the features that most keeps me there is message-mode : it’s the best way I know to write, edit, and create words for others to read. But for all its excellence, it’s a tool from the 1980s. It assumes a world of plain text email. That’s often fine for a techie—most of my world is plain text email.

Emacs message-mode still thinks of ideas like attachments as a feature worth mentioning. But I work with people who treat not only attachments, but rich HTML text, tables, and graphics as an essential part of communication. I see the benefits of those too—especially in a world where orders of magnitude people use smartphones for e-mail than use terminal windows with fixed-width fonts! I’ve been using a little script called mimedown for ages. I originally started with a 2008 blog post:

( defun mimedown () () (interactive) (save-excursion (message-goto-body) ( let* ((sig-point (save-excursion (message-goto-signature) (forward-line -1 ) (point))) ((sig-point (save-excursion (message-goto-signature) (forward-line) (point))) (orig-txt (buffer-substring-no-properties (point) sig-point))) "Markdown.pl" nil t ) (shell-command-on-region (point) sig-point "<#multipart type=alternative>

" ) (insert (insert orig-txt) "<#part type=text/html>

< html>

< head>

< title> HTML version of email</title>

</head>

< body>" ) (insert (exchange-point-and-mark) "

</body>

</html>

<#/multipart>

" )))) (insert))))

But who uses Markdown.pl these days? So that got changed pretty fast to:

( defun mimedown () () (interactive) (save-excursion (message-goto-body) (mml-unsecure-message) ( let* ((sig-point (save-excursion (message-goto-signature) (forward-line -1 ) (point))) ((sig-point (save-excursion (message-goto-signature) (forward-line) (point))) (orig-txt (buffer-substring-no-properties (point) sig-point))) "/usr/local/bin/pandoc --from markdown --to html --smart --standalone" nil t ) (shell-command-on-region (point) sig-point "<#multipart type=alternative>

" ) (insert (insert orig-txt) "<#part type=text/html>

" ) (insert (exchange-point-and-mark) "

<#/multipart>

" )))) (insert))))

That works with the lovely table support and footnotes available in Pandoc. But now I’ve learned about the Lua filters available in recent Pandoc—and I can’t count the number of times I wish I could show a diagram to a colleague, but can’t rely on ASCII art to get there. Therefore:

( defun mimedown () () "For writing pretty mail" (interactive) (save-excursion (message-goto-body) (mml-unsecure-message) ( let* ((sig-point (save-excursion (message-goto-signature) (forward-line -1 ) (point))) ((sig-point (save-excursion (message-goto-signature) (forward-line) (point))) (orig-txt (buffer-substring-no-properties (point) sig-point))) "/Users/bts/bin/pandoc --from markdown+smart --to html --self-contained --lua-filter=diagram-generator.lua --metadata pagetitle=Mail --metadata plantumlPath=/usr/local/Cellar/plantuml/1.2019.9/libexec/plantuml.jar --template mimedown.html" nil t "*mimedown-errors*" ) (shell-command-on-region (point) sig-point "<#multipart type=alternative>

" ) (insert (insert orig-txt) "<#part type=text/html>

" ) (insert (exchange-point-and-mark) "

<#/multipart>

" )))) (insert))))

Now I can write mail that uses sequence diagrams, ditaa, graphviz, tikz, even matplotlib and has the images both properly processed and included directly—as data URLs, not attachments—so they forward reliably. Even better, the text/plain version is crystal clear (and the preferred form for modifying the diagram!). Here, take a look:

I write

```{.plantuml caption="This is an image, created by **PlantUML**."} @startuml Alice -> Bob: Authentication Request Bob --> Alice: Authentication Response Alice -> Bob: Another authentication Request Alice <-- Bob: another Response @enduml ```

The text/plain version shows that directly. And the text/html part says:

<img src= "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBjb250ZW50U2NyaXB0VHlwZT0iYXBwbGljYXRpb24vZWNtYXNjcmlwdCIgY29udGVudFN0eWxlVHlwZT0idGV4dC9jc3MiIGhlaWdodD0iMjE2cHgiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiIHN0eWxlPSJ3aWR0aDoyODVweDtoZWlnaHQ6MjE2cHg7IiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAyODUgMjE2IiB3aWR0aD0iMjg1cHgiIHpvb21BbmRQYW49Im1hZ25pZnkiPjxkZWZzPjxmaWx0ZXIgaGVpZ2h0PSIzMDAlIiBpZD0iZnR3NGY5..." alt />

But shows up to most people as:

This is so cool to me—look at the source of that image!

The hard part is getting Java/PlantUML, Graphviz, Tikz, Inkscape, Python, and all installed and accessible to each other. To work with a Mac and Homebrew, I have some modifications at https://github.com/briansniffen/lua-filters ; it’s just a few lines to deal with Inkscape having a non-FHS app bundle. The only other detail is the trivial template to throw away the HTML header, ~/.pandoc/templates/mimedown.html :

$for(include-before)$ $include-before$ $endfor$ $body$ $for(include-after)$ $include-after$ $endfor$

I’m happy to chat about how to make this work well for you, and I look forward to getting more beautiful—but still usable—mail!