Recipe 13.5. Sending HTML Mail
Credit: Art Gillespie
Problem
You 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.
Solution
Although 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
Discussion
This 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_ _":Sending HTML mail is a popular concept, and (as long as you avoid
import smtplib
f = open("newsletterl", '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 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 Also
Recipe 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.