Sometimes I need to send a fragment of code on google-group inline. Text does not help here; I can type it in markdown, convert it to html (using pandoc etc.), attach to mutt as text/html and send it.

There is one good solution available here but it uses external sendmail program to send email. I am using mutt which has capabilities to send emails over IMAP by itself.

  • Why not just pipe output from a command-line Markdown formatter to sendmail? – Naftuli Kay Jan 9 '14 at 2:26
  • Hmm.. shared computer! Don't want to store password for external sendmail. – Dilawar Jan 9 '14 at 2:35
  • Is there any examples we can see of what your current results look like on google-groups? – slm Jan 9 '14 at 3:44
  • Also you want to type things up in markdown, but have them rendered prior to attaching them to your emails, right? – slm Jan 9 '14 at 3:53
  • This sounds like what you're asking for, but might need to be modified: dgl.cx/2009/03/html-mail-with-mutt-using-markdown. Also markdownmail.py sounded like something you could use. – slm Jan 9 '14 at 3:55

After you compose a message, but before sending you have lots of options available to you. Press ? to view them.

Some that may help here:

  • F to filter the attachment through an external processor
    • Use pandoc -s -f markdown -t html to convert to HTML
  • ^T to edit the attachment MIME type
    • Change from text/plain to text/html.

Now a macro that will do everything in one step. Add this to your .muttrc:

macro compose \e5 "F pandoc -s -f markdown -t html \ny^T^Utext/html; charset=us-ascii\n"
set wait_key=no

To use this macro, after you have finished composing your message but before you send, press Esc then 5 to convert your markdown formatted message into HTML.

You can naturally customize this macro as you see fit. Mutt has lots of key bindings already built in, so whatever key sequence you choose to bind to, make sure it doesn't overwrite something else (or it's something you can live without).

The option set wait_key=no suppresses Mutt's Press any key to continue... prompt when external commands are run. If wait_key is yes (which is the default) you'll have to press Esc, then 5, then any other key to continue.

  • 1
    This is a really elegant solution! +1 – sinisterstuf Sep 23 '15 at 9:51
  • 4
    this is nice, but it's got core flaw. it eliminates the plaintext part of the email which makes it suck to read in clients like ... mutt ;) HTML emails should have a plaintext and html component. the raw markdown should be the plaintext, the converted should be the HTML. – masukomi Jun 17 '16 at 15:27
  • 1
    Agree with @masukomi, email clients in general send both html and text versions of the email. It would be nice to have a macro that adds the html version and leaves the original as text/plain. – pepper_chico Aug 25 '17 at 17:30
  • 2
    In the end I've build my own setup for this nosubstance.me/post/mutt-secret-sauce – pepper_chico Sep 20 '17 at 17:37
  • 1
    @Rastapopoulos yes: github.com/oblitum/dotfiles/issues/3#issuecomment-437954693 – pepper_chico Nov 16 '18 at 12:21

Sendmail is often not flexible enogh for sending mails.

I use msmtp together with mutt on particular accounts for flexible SMTP.

To use it with mutt change:

# ~/.muttrc  
set sendmail="/usr/bin/msmtp -a default"   


# ~/.msmtprc  
tls off
logfile ~/.msmtp.log  
account default   
host your.smtp.host  
port 25  
from your-user-name@your-host.com  
auth off  
user username  
password password  

I was able to do it. I am not entirely happy about my solution but it is decent enough. Waiting for someone else to provide a better solution.

The process is following. Convert the markdown to html and attach it to message. Turn this attachment to inline attachment. But now I have two attachments, first is in markdown and second is html. Replace the markdown content with empty string so only html is sent.

I added the following line to ~/.muttrc file.

macro compose B ":set editor=text2mime-markdown.py<enter>E:set editor=email-editor<enter>Da/tmp/html-markdown-alternative.html<enter>^Du"

Here is email-editor which is borrowed from the link posted in question.

if grep -q In-Reply-To $1; then
  # Jump to first line of message
  exec vim -c 'norm }j' $1
  # Enter insert mode on the To: line
  exec vim  $1

And the main python file which is called is following. This is inspired from the perl script from the link in question.

#!/usr/bin/env python
import os
import sys
from formatter import *
version = "0.1"

file = sys.argv[1]
new_file = "/tmp/html-markdown-alternative.html"
with open(file, "r") as f:
    text = f.read()

lines = text.split('\n')
header = []
body = []
headerStart = True
for l in lines:
    if headerStart:
        m = re.search(r'^[\w\-]+\:', l)
        if m:
            headerStart = False

header = '\n'.join(header)
body = '\n'.join(body)

htmlBody = markdownToHtml(body);

html = []
html.append('<meta name=\"generator\" content=\"text2mime-markdown{}\">'.format(version))
html.append("code { font-family: 'Andale Mono', 'Lucida Console', \
        'Bitstream Vera Sans Mono', 'Courier New', monospace; }")
html.append('pre { border-left: 20px solid #ddd; margin-left: 10px; \
        padding-left: 5px; }')
html = '\n'.join(html)

with open(new_file, "w") as newF:

with open(file, 'w') as f:

This depends on one more python file called formatter.py which uses pandoc to format my mail but if pandoc is not available it can use python-markdown2 package. This script is following.

import subprocess
import re
import os 
import sys
import html2text 
import collections

# check if pandoc exists
panDoc = True
    subprocess.call(["pandoc", '--version']
            , stdout=subprocess.PIPE
            , stdin=subprocess.PIPE
except OSError:
    panDoc = False

if not panDoc:
    import text.html2text as html2text
    import markdown 

def decodeText(text):
    return text.decode('utf-8')

def markdownToHtml(content, convertor='pandoc'):
    global panDoc
    if panDoc:
        cmd = ["pandoc", "-f", "markdown", "-t", "html"]
        p = subprocess.Popen(cmd
                , stdin = subprocess.PIPE
                , stdout = subprocess.PIPE
        content = p.communicate()[0]
        return decodeText(content)
        return markdown.markdown(decodeText(content))

def htmlToMarkdown(content, convertor='pandoc'):
    global panDoc
    if panDoc and convertor == 'pandoc':
        cmd = ["pandoc", "-t", "markdown", "-f", "html"]
        p = subprocess.Popen(cmd
                , stdin = subprocess.PIPE
                , stdout = subprocess.PIPE
        content = p.communicate()[0]
        return decodeText(content)
    # Use markdown package to convert markdown to html
        h = html2text.HTML2Text()
        content = h.handle(decodeText(content))
        return content

def titleToBlogDir(title):
    if title is None:
        return ''
    if len(title.strip()) == 0:
        return ''
    blogDir = title.replace(" ","_").replace(':', '-').replace('(', '')
    blogDir = blogDir.replace('/', '').replace('\\', '').replace('`', '')
    blogDir = blogDir.replace(')', '').replace("'", '').replace('"', '')
    return blogDir

def titleToFilePath(title, blogDir):
    if len(blogDir.strip()) == 0:
        return ''
    fileName = os.path.join(blogDir, titleToBlogDir(title))
    return fileName

def htmlToHtml(html):
    return decodeText(html)

def metadataDict(txt):
    mdict = collections.defaultdict(list)
    md = getMetadata(txt)
    for c in ["title", 'type', "layout", "status", "id", "published", "category", "tag"]:
        pat = re.compile(r'{0}\:\s*(?P<name>.+)'.format(c), re.IGNORECASE)
        m = pat.findall(txt)
        for i in m:
    return mdict

def getMetadata(txt):
   Get metadata out of a txt
   if not "---" in txt:
       print txt

   pat = re.compile(r'\-\-\-+(?P<metadata>.+?)\-\-\-+', re.DOTALL)
   m = pat.search(txt)
   if m:
       return m.group('metadata')

def getContent(txt):
    Return only text of the post.
    pat = re.compile(r'\-\-\-+(?P<metadata>.+?)\-\-\-+', re.DOTALL)
    return re.sub(pat, "", txt)

def readInputFile(fileName):
    read file and return its format. html or markdown
    assert fileName
    if not os.path.exists(fileName):
        raise IOError, "File %s does not exists" % fileName

    # Check the fmt of file.
    fmt = os.path.splitext(fileName)[1].lower()
    if fmt in ["htm", "html", "xhtml"]:
        fmt = "html"
    elif fmt in ["md", "markdown"]:
        fmt = "markdown"
        fmt = "markdown"
    txt = open(fileName, 'r').read()   
    return (fmt, txt)

def formatContent(txt, fmt):
    Format the content as per fmt.
    content = getContent(txt)
    if fmt == "html":
        content = htmlToHtml(content)
    elif fmt == "markdown":
        content = markdownToHtml(content)
        content = markdownToHtml(content)
    return content

These files are also available here https://github.com/dilawar/mutt


I can send email in any format using neomutt. I just use Emacs (org-mode), instead of vim. Although, I am a vim user as well. But, I mostly use Emacs with evil-mode.

In my .muttrc I have set up the editor to be emacs instead of vim. When writing a new email, neomutt fires emacs up. Then I call "org-mode", write the message, and export to whatever format I want.

I can export to PDF format. Then I save it and attach the PDF file in my /tmp. After that I can send to whomever.

If I want the html format, I export that in the same way and I automatically can see the output, before sending the email.

Apart from that, there are many other export formats in org-mode. Just, choose what you want. For sending code to other people, just add the source code to whatever language you want. Everything is explained in the org-wiki.


You can send e-mails also as a multipart/alternative containing both text/plain and text/html.

Requirements: pandoc

Basically it creates from markdown message plaintext and html5. Creates attachments from those parts, marks them as inline attachments, set correct mime type and combine them into mutlipart message.

Any other attachments are supposed to be added after running this macro in compose menu. Optionally signing/encrypting message should be done as the final step

macro compose ,m \
"<enter-command>set pipe_decode<enter>\
<pipe-message>pandoc -f gfm -t plain -o /tmp/msg.txt<enter>\
<pipe-message>pandoc -s -f gfm -t html5 -o /tmp/msg.html<enter>\
<enter-command>unset pipe_decode<enter>a^U/tmp/msg.txt\n^Da^U/tmp/msg.html\n^D^T^Utext/html; charset=utf-8\n=DTT&d^U\n" \
"Convert markdown gfm to HTML and plain" 

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.