Bundling Files in a MIME Message

Credit: Matthew Dixon Cowles

Problem

You want to create a multipart MIME message that includes all files in the current directory.

Solution

If you often deal with composing or parsing mail messages, or mail-like messages such as Usenet news posts, the new email module gives you sparkling new opportunities:

#!/usr/bin/env python

import base64, quopri
import mimetypes, email.Generator, email.Message
import cStringIO, os

# sample addresses
toAddr="example@example.com"
fromAddr="example@example.com"
outputFile="dirContentsMail"

def main(  ):
    mainMsg = email.Message.Message(  )
    mainMsg["To"] = toAddr
    mainMsg["From"] = fromAddr
    mainMsg["Subject"] = "Directory contents"
    mainMsg["Mime-version"] = "1.0"
    mainMsg["Content-type"] = "Multipart/mixed"
    mainMsg.preamble = "Mime message\n"
    mainMsg.epilogue = "" # to ensure that message ends with newline
    # Get names of plain files (not subdirectories or special files)
    fileNames = [f for f in os.listdir(os.curdir) if os.path.isfile(f)]
    for fileName in fileNames:
        contentType,ignored = mimetypes.guess_type(fileName)
        if contentType==None: # If no guess, use generic opaque type
            contentType = "application/octet-stream"
        contentsEncoded = cStringIO.StringIO(  )
        f = open(fileName, "rb")
        mainType = contentType[:contentType.find("/")]
        if mainType=="text":
            cte = "quoted-printable"
            quopri.encode(f, contentsEncoded, 1) # 1 to encode tabs
        else:
            cte = "base64"
            base64.encode(f, contentsEncoded)
        f.close(  )
        subMsg = email.Message.Message(  )
        subMsg.add_header("Content-type", contentType, name=fileName)
        subMsg.add_header("Content-transfer-encoding", cte)
        subMsg.add_payload(contentsEncoded.getvalue(  ))
        contentsEncoded.close(  )
        mainMsg.add_payload(subMsg)

    f = open(outputFile,"wb")
    g = email.Generator.Generator(f)
    g(mainMsg)
    f.close(  )
    return None

if _ _name_ _=="_ _main_ _":
    main(  )

Discussion

The email module, new in Python 2.2, makes manipulating MIME messages easier than it used to be (with the standard Python library modules already present in Python 2.1 and earlier). This is not a trivial point, so this recipe’s example may be useful. See the standard Library Reference for detailed documentation about the email module.

MIME (Multipurpose Internet Mail Extensions) is the Internet standard for sending files and non-ASCII data by email. The standard is specified in RFCs 2045-2049. There are a few points that are especially worth keeping in mind:

  • The original specification for the format of an email (RFC 822) didn’t allow for non-ASCII characters and had no provision for attaching or enclosing a file along with a text message. Therefore, not surprisingly, MIME messages are very common these days.

  • Messages that follow the MIME standard are backward-compatible with ordinary RFC 822 (now RFC 2822) messages. A mail reader that doesn’t understand the MIME specification will probably not be able to display a MIME message in a way that’s useful to the user, but the message will be legal and therefore shouldn’t cause unexpected behavior.

  • An RFC 2822 message consists of a set of headers, a blank line, and a body. MIME handles attachments and other multipart documents by specifying a format for the message’s body. In multipart MIME messages, the body is divided into submessages, each of which has a set of headers, a blank line, and a body. Generally, each submessage is referred to a MIME part, and parts may nest recursively.

  • MIME parts (whether in a multipart message or not) that contain characters outside of the strict US-ASCII range are encoded as either base-64 or quoted-printable data, so that the resulting mail message contains only ordinary ASCII characters. Data can be encoded with either method, but generally, only data that has few non-ASCII characters (basically text, possibly with a few extra characters outside of the ASCII range, such as national characters in Latin-1 and similar codes) is worth encoding as quoted-printable, because even without decoding it may be readable. If the data is essentially binary, with all bytes being equally likely, base-64 encoding is more compact.

Not surprisingly, given all that, manipulating MIME messages is often considered to be a nuisance. Before Python 2.2, the standard library’s modules for dealing with MIME messages were quite useful but rather miscellaneous. In particular, putting MIME messages together and taking them apart required two distinct approaches. The email module, new in Python 2.2, unifies and simplifies these two related jobs.

See Also

Recipe 10.12 shows how the email module can be used to unpack a MIME message; documentation for the standard library modules email, smtplib, mimetypes, base64, quopri, and cStringIO in the Library Reference. attachments.

Get Python Cookbook now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.