Index: django/core/mail.py
===================================================================
--- django/core/mail.py	(revision 4198)
+++ django/core/mail.py	(working copy)
@@ -1,6 +1,13 @@
 # Use this module for e-mailing.
 
 from django.conf import settings
+import mimetypes
+from email import Encoders
+from email.Message import Message
+from email.MIMEAudio import MIMEAudio
+from email.MIMEBase import MIMEBase
+from email.MIMEMultipart import MIMEMultipart
+from email.MIMEImage import MIMEImage
 from email.MIMEText import MIMEText
 from email.Header import Header
 import smtplib, rfc822
@@ -22,12 +29,41 @@
             val = Header(val, settings.DEFAULT_CHARSET)
         MIMEText.__setitem__(self, name, val)
 
-def send_mail(subject, message, from_email, recipient_list, fail_silently=False, auth_user=settings.EMAIL_HOST_USER, auth_password=settings.EMAIL_HOST_PASSWORD):
+class SafeMIMEMultipart(MIMEMultipart):
+    def __setitem__(self, name, val):
+        "Forbids multi-line headers, to prevent header injection."
+        if '\n' in val or '\r' in val:
+            raise BadHeaderError, "Header values can't contain newlines (got %r for header %r)" % (val, name)
+        if name == "Subject":
+            val = Header(val, settings.DEFAULT_CHARSET)
+        MIMEMultipart.__setitem__(self, name, val)
+
+    def attachFile(self, filename, fp):
+        ctype, encoding = mimetypes.guess_type(filename)
+        if ctype is None or encoding is not None:
+            ctype = 'application/octet-stream'
+        maintype, subtype = ctype.split('/', 1)
+        if maintype == 'text':
+            msg = SafeMIMEText(fp.read(), _subtype=subtype, _charset=settings.DEFAULT_CHARSET)
+        elif maintype == 'image':
+            msg = MIMEImage(fp.read(), _subtype=subtype)
+        elif maintype == 'audio':
+            msg = MIMEAudio(fp.read(), _subtype=subtype)
+        else:
+            msg = MIMEBase(maintype, subtype)
+            msg.set_payload(fp.read())
+            # Encode the payload using Base64
+            Encoders.encode_base64(msg)
+        # Set the filename parameter
+        msg.add_header('Content-Disposition', 'attachment', filename=filename)
+        MIMEMultipart.attach(self, msg)
+
+def send_mail(subject, message, from_email, recipient_list, fail_silently=False, auth_user=settings.EMAIL_HOST_USER, auth_password=settings.EMAIL_HOST_PASSWORD, attachments=None):
     """
     Easy wrapper for sending a single message to a recipient list. All members
     of the recipient list will see the other recipients in the 'To' field.
     """
-    return send_mass_mail([[subject, message, from_email, recipient_list]], fail_silently, auth_user, auth_password)
+    return send_mass_mail([[subject, message, from_email, recipient_list, attachments]], fail_silently, auth_user, auth_password)
 
 def send_mass_mail(datatuple, fail_silently=False, auth_user=settings.EMAIL_HOST_USER, auth_password=settings.EMAIL_HOST_PASSWORD):
     """
@@ -46,11 +82,26 @@
             return
         raise
     num_sent = 0
-    for subject, message, from_email, recipient_list in datatuple:
+    for i in datatuple:
+        try:
+            subject, message, from_email, recipient_list, attachments = i
+        except ValueError:
+            subject, message, from_email, recipient_list = i
+            attachments = None
         if not recipient_list:
             continue
         from_email = from_email or settings.DEFAULT_FROM_EMAIL
-        msg = SafeMIMEText(message, 'plain', settings.DEFAULT_CHARSET)
+        body = SafeMIMEText(message, 'plain', settings.DEFAULT_CHARSET)
+        if attachments:
+            # This is a multipart mail
+            msg = SafeMIMEMultipart()
+            # First the body
+            msg.attach(body)
+            # Then the various files to be attached.
+            for (filename, fp) in attachments:
+                msg.attachFile(filename, fp)
+        else:
+            msg = body
         msg['Subject'] = subject
         msg['From'] = from_email
         msg['To'] = ', '.join(recipient_list)
Index: docs/email.txt
===================================================================
--- docs/email.txt	(revision 4198)
+++ docs/email.txt	(working copy)
@@ -35,7 +35,7 @@
 
     send_mail(subject, message, from_email, recipient_list,
         fail_silently=False, auth_user=EMAIL_HOST_USER,
-        auth_password=EMAIL_HOST_PASSWORD)
+        auth_password=EMAIL_HOST_PASSWORD, attachments=None)
 
 The ``subject``, ``message``, ``from_email`` and ``recipient_list`` parameters
 are required.
@@ -55,9 +55,25 @@
     * ``auth_password``: The optional password to use to authenticate to the
       SMTP server. If this isn't provided, Django will use the value of the
       ``EMAIL_HOST_PASSWORD`` setting.
+    * ``attachments``: A list of ``(filename, fp)`` tuples specifying
+      attachements.
 
 .. _smtplib docs: http://www.python.org/doc/current/lib/module-smtplib.html
 
+Sending mail with attachments
+-----------------------------
+
+``attachments`` is a list of ``(filename, fp)`` tuples. ``filename`` specifes
+the name of the file to be attached, and ``fp`` is the file stream. To attach
+an image and an audio file pass
+
+    attachments = [
+        ('image.png', open('/home/user/image.png', 'rb')),
+        ('audio.mp3', open('/home/user/audio.mp3', 'rb')),
+    ]
+
+to the ``send_mail`` function.
+
 send_mass_mail()
 ================
 
@@ -67,10 +83,14 @@
     send_mass_mail(datatuple, fail_silently=False,
         auth_user=EMAIL_HOST_USER, auth_password=EMAIL_HOST_PASSWORD):
 
-``datatuple`` is a tuple in which each element is in this format::
+``datatuple`` is a tuple in which each element is either in the format::
 
     (subject, message, from_email, recipient_list)
 
+or::
+
+    (subject, message, from_email, recipient_list, attachments)
+
 ``fail_silently``, ``auth_user`` and ``auth_password`` have the same functions
 as in ``send_mail()``.
 
