Recipe 13.5. Sending HTML MailCredit: Art Gillespie ProblemYou need to send HTML mail and accompany it with a plain text version of the message's contents, so that the email message is also readable by MUAs that are not HTML-capable. SolutionAlthough the modern Python way to perform any mail manipulation is with the standard Python library email package, the functionality we need for this recipe is also supplied by the MimeWriter and mimetools modules (which are also in the Python Standard Library). We can easily code a function that just accesses and uses that functionality: def createhtmlmail(subject, html, text=None): " Create a mime-message that will render as HTML or text, as appropriate" import MimeWriter, mimetools, 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) # input buffer for the HTML txtin = cStringIO.StringIO(text) # input buffer for the plain text writer = MimeWriter.MimeWriter(out) # Set up some basic headers. Place subject here because smtplib.sendmail # expects it to be in the message, 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 as a string writer.lastpart( ) msg = out.getvalue( ) out.close( ) return msg DiscussionThis recipe's 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 you do send HTML mail, never forget to embed a text-only version of your message along with the HTML version. Lots of folks still prefer character-mode mail readers (technically known as MUAs), and it makes no sense to alienate those users by sending mail that they can't conveniently read. This recipe shows how easy Python makes the task of sending an email in both HTML and text forms. 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 such nice textual input, you can still prepare a text version on the fly starting from the HTML version; one way to prepare such text 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 then 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. The emails generated by this code have been successfully read 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, other such MUAs can be expected to work just as acceptably. See AlsoRecipe 13.6 shows how the email package in the Python Standard Library can also be used to compose a MIME multipart message; documentation in the Library Reference and Python in a Nutshell about the standard library package email, as well as modules mimetools, MimeWriter, htmllib, formatter, cStringIO, and smtplib; Henry Minsky's article about MIME (http://www.arsdigita.com/asj/mime/) for information on various issues related to sending HTML mail. |