Brool brool (n.) : a low roar; a deep murmur or humming

Using Markdown with Mutt

 |  python coding

This took me a while to figure out, so in the hopes that I can save someone some time, here’s how to use markdown with Mutt:

Step 1: Install msmtp (or any other program)

On OS X you can do this with “brew install msmtp”. (You can use sendmail or whatever, but msmtp was easy to set up.).

Step 2: Set up msmtp

Set up the appropriate configuration file. I ran into a problem wherein the program would just hang; turning on debug showed that it hung after “Reading recipients from command line.” This turned out to be an incorrect SSL configuration – I needed tls_starttls to be off.

My particular .msmtprc configuration:

defaults tls on tls_starttls off tls_certcheck off account Work host domain auth on port 465 protocol smtp from from-name user user-name password password

Step 3: install sendmail replacement

For me, this was two things: a small shell script and a Python program that will convert any plaintext e-mail into a plaintext + HTML e-mail.

Shell script mark_send:

#!/bin/sh /Users/tim/mutt/ "/usr/local/bin/pandoc -s" | /usr/local/bin/msmtp -a Work $@

Python script

#!/usr/bin/python import os import sys import subprocess import email import email.parser import tempfile from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText if len(sys.argv) == 1: print "usage: markdown_program [markdown_flag]" print "where:" print " markdown_program is the name of the program to translate markdown." print " markdown_flag is an optional indicator in the first line that determines whether to run the markdown program" sys.exit(0) markdown_program = sys.argv[1] p = email.parser.Parser() m = p.parsestr( # is this a single header? do_markdown = False if not m.is_multipart() and m.get_content_maintype() == 'text' and m.get_content_subtype() == 'plain': do_markdown = True payload = m.get_payload() # optionally check the markdown flag if len(sys.argv) == 3: lines = payload.split("\n") do_markdown = lines[0] == sys.argv[2] if do_markdown: payload = "\n".join(lines[1:]) if do_markdown: tf = tempfile.NamedTemporaryFile(delete=False) tf.write(payload) tf.close() (markdown, error) = subprocess.Popen(markdown_program.split() + [], stdout=subprocess.PIPE).communicate() # create a new message with the exact same headers new_message = MIMEMultipart('alternative') for (k,v) in m._headers: new_message[k] = v text_plain = MIMEText(payload, 'plain') new_message.attach(text_plain) text_html = MIMEText(markdown, 'html') new_message.attach(text_html) print new_message.as_string() os.unlink( else: print m.as_string()

The program takes one required argument (the name of the program to convert from markdown to HTML, possibly quoted) an an optional indicator of markdown. If the first line of the message is the indicator, then it is stripped and the rest of the message is converted to HTML.

Step 3: set up mutt

You need to tell mutt to use your shell script instead of sendmail. I have it inside a folder hook:

folder-hook +work/.* 'set sendmail="/bin/sh /Users/tim/mutt/mark_send"'

Now, whenever you send a plain-text email, mutt will automatically run markdown on it and attach it as the HTML view of the message.


So, after running with this for a few days, here are my impressions:

Example is show below, as rendered by Zimbra. It isn’t perfect. Google will unfortunately strip the colors from the code coloring, and Zimbra loses leading indents, and both readers will require the user to load images. Nonetheless, in a pinch…


Comments are moderated whenever I remember that I have a blog.

Christopher Valerio | 2015-09-29 22:37:51
Do you have a method to use this but to make it multipart, so that I can have 2 versions 1 in text, and another html?
Enrico | 2014-11-01 19:06:49
This is a good script but I am using a faster way using Pandoc. I simply bound a macro command in order to have the file converted to html before sending. macro compose \e5 "F pandoc -s -f markdown -t html \ny^T^Utext/html; charset=utf-8\n"
Add a comment