I l@ve RuBoard Previous Section Next Section

10.9 Sending HTML Mail

Credit: Art Gillespie

10.9.1 Problem

You need to send HTML mail and embed a message version in plain text, so that the message is also readable by MUAs that are not HTML-capable.

10.9.2 Solution

The key functionality is supplied by the MimeWriter and mimetools modules:

def createhtmlmail(subjectl, html, text=None):
    "Create a mime-message that will render as HTML or text, as appropriate"
    import MimeWriter
    import mimetools
    import cStringIO

    if text is None:
        # Produce an approximate textual rendering of the HTML string,
        # unless you have been given a better version as an argument
        import htmllib, formatter
        textout = cStringIO.StringIO(  )
        formtext = formatter.AbstractFormatter(formatter.DumbWriter(textout))
        parser = htmllib.HTMLParser(formtext)
        parser.feed(html)
        parser.close(  )
        text = textout.getvalue(  )
        del textout, formtext, parser

    out = cStringIO.StringIO(  ) # output buffer for our message
    htmlin = cStringIO.StringIO(html)
    txtin = cStringIO.StringIO(text)

    writer = MimeWriter.MimeWriter(out)

    # Set up some basic headers. Place subject here
    # because smtplib.sendmail expects it to be in the
    # message body, as relevant RFCs prescribe.
    writer.addheader("Subject", subject)
    writer.addheader("MIME-Version", "1.0")

    # Start the multipart section of the message.
    # Multipart/alternative seems to work better
    # on some MUAs than multipart/mixed.
    writer.startmultipartbody("alternative")
    writer.flushheaders(  )

    # the plain-text section: just copied through, assuming iso-8859-1
    subpart = writer.nextpart(  )
    pout = subpart.startbody("text/plain", [("charset", 'iso-8859-1')])
    pout.write(txtin.read(  ))
    txtin.close(  )

    # the HTML subpart of the message: quoted-printable, just in case
    subpart = writer.nextpart(  )
    subpart.addheader("Content-Transfer-Encoding", "quoted-printable")
    pout = subpart.startbody("text/html", [("charset", 'us-ascii')])
    mimetools.encode(htmlin, pout, 'quoted-printable')
    htmlin.close(  )

    # You're done; close your writer and return the message body
    writer.lastpart(  )
    msg = out.getvalue(  )
    out.close(  )
    return msg

10.9.3 Discussion

This module is completed in the usual style with a few lines to ensure that, when run as a script, it runs a self-test by composing and sending a sample HTML mail:

if _ _name_ _=="_ _main_ _":
    import smtplib
    f = open("newsletter.html", 'r')
    html = f.read(  )
    f.close(  )
    try:
        f = open("newsletter.txt", 'r')
        text = f.read(  )
    except IOError:
        text = None
    subject = "Today's Newsletter!"
    message = createhtmlmail(subject, html, text)
    server = smtplib.SMTP("localhost")
    server.sendmail('agillesp@i-noSPAMSUCKS.com',
        'agillesp@i-noSPAMSUCKS.com', message)
    server.quit(  )

Sending HTML mail is a popular concept, and as long as you avoid sending it to newsgroups and open mailing lists, there's no reason your Python scripts shouldn't do it. When they do, don't forget to embed two alternative versions of your message: the HTML version and a text-only version. Lots of folks still prefer character-mode mail readers (technically known as a mail user agent, or MUA), and it makes no sense to alienate them by sending mail that they can't conveniently read. This recipe shows how easy Python makes this.

Ideally, your input will be a properly formatted text version of the message, as well as the HTML version. But if you don't have this input, you can still prepare a text version on the fly; one way to do this is shown in the recipe. Remember that htmllib has some limitations, so you may want to use alternative approaches, such as saving the HTML string to disk and using:

text=os.popen('lynx -dump %s'%tempfile).read(  )

or whatever works best for you. Alternatively, if all you have as input is plain text (following some specific conventions, such as empty lines to mark paragraphs and underlines for emphasis), you can parse the text and throw together some HTML markup on the fly. See Recipe 12.8 for some ideas on how to synthesize structured-text markup from plain text following these rather common conventions.

The emails generated by this code have been successfully tested on Outlook 2000, Eudora 4.2, Hotmail, and Netscape Mail. It's likely that they will work in other HTML-capable MUAs as well. MUTT has been used to test the acceptance of messages generated by this recipe in text-only MUAs. Again, others would be expected to work just as acceptably.

10.9.4 See Also

Recipe 10.11 shows how Python 2.2's email package can be used to compose a MIME multipart message; Recipe 12.8 for other text synthesis options; documentation for the standard module email for a Python 2.2 alternative to classic Python modules such as mimetools and MimeWriter; Henry Minsky's article on MIME (http://www.arsdigita.com/asj/mime/) for information on the issues of how to send HTML mail.

    I l@ve RuBoard Previous Section Next Section