diff -r 4e009aed3ca6 django/conf/global_settings.py
--- a/django/conf/global_settings.py	Sat Aug 29 12:40:47 2009 +0000
+++ b/django/conf/global_settings.py	Wed Sep 02 12:05:20 2009 +0200
@@ -131,6 +131,12 @@
 DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3.
 DATABASE_OPTIONS = {}          # Set to empty dictionary for default.
 
+# The email backend to use. For possible shortcuts see django.core.mail.
+# The default is to use the 'smtp' backend for sending emails using a
+# SMTP server.
+# Third-party backends could be configured by a Python import path.
+EMAIL_BACKEND = 'smtp'
+
 # Host for sending e-mail.
 EMAIL_HOST = 'localhost'
 
@@ -142,6 +148,9 @@
 EMAIL_HOST_PASSWORD = ''
 EMAIL_USE_TLS = False
 
+# Directory for saving messages when using the file backend.
+EMAIL_FILE_PATH = None
+
 # List of strings representing installed apps.
 INSTALLED_APPS = ()
 
diff -r 4e009aed3ca6 django/core/mail.py
--- a/django/core/mail.py	Sat Aug 29 12:40:47 2009 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,426 +0,0 @@
-"""
-Tools for sending email.
-"""
-
-import mimetypes
-import os
-import smtplib
-import socket
-import time
-import random
-from email import Charset, Encoders
-from email.MIMEText import MIMEText
-from email.MIMEMultipart import MIMEMultipart
-from email.MIMEBase import MIMEBase
-from email.Header import Header
-from email.Utils import formatdate, parseaddr, formataddr
-
-from django.conf import settings
-from django.utils.encoding import smart_str, force_unicode
-
-# Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from
-# some spam filters.
-Charset.add_charset('utf-8', Charset.SHORTEST, Charset.QP, 'utf-8')
-
-# Default MIME type to use on attachments (if it is not explicitly given
-# and cannot be guessed).
-DEFAULT_ATTACHMENT_MIME_TYPE = 'application/octet-stream'
-
-# Cache the hostname, but do it lazily: socket.getfqdn() can take a couple of
-# seconds, which slows down the restart of the server.
-class CachedDnsName(object):
-    def __str__(self):
-        return self.get_fqdn()
-
-    def get_fqdn(self):
-        if not hasattr(self, '_fqdn'):
-            self._fqdn = socket.getfqdn()
-        return self._fqdn
-
-DNS_NAME = CachedDnsName()
-
-# Copied from Python standard library, with the following modifications:
-# * Used cached hostname for performance.
-# * Added try/except to support lack of getpid() in Jython (#5496).
-def make_msgid(idstring=None):
-    """Returns a string suitable for RFC 2822 compliant Message-ID, e.g:
-
-    <20020201195627.33539.96671@nightshade.la.mastaler.com>
-
-    Optional idstring if given is a string used to strengthen the
-    uniqueness of the message id.
-    """
-    timeval = time.time()
-    utcdate = time.strftime('%Y%m%d%H%M%S', time.gmtime(timeval))
-    try:
-        pid = os.getpid()
-    except AttributeError:
-        # No getpid() in Jython, for example.
-        pid = 1
-    randint = random.randrange(100000)
-    if idstring is None:
-        idstring = ''
-    else:
-        idstring = '.' + idstring
-    idhost = DNS_NAME
-    msgid = '<%s.%s.%s%s@%s>' % (utcdate, pid, randint, idstring, idhost)
-    return msgid
-
-class BadHeaderError(ValueError):
-    pass
-
-def forbid_multi_line_headers(name, val):
-    """Forbids multi-line headers, to prevent header injection."""
-    val = force_unicode(val)
-    if '\n' in val or '\r' in val:
-        raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name))
-    try:
-        val = val.encode('ascii')
-    except UnicodeEncodeError:
-        if name.lower() in ('to', 'from', 'cc'):
-            result = []
-            for item in val.split(', '):
-                nm, addr = parseaddr(item)
-                nm = str(Header(nm, settings.DEFAULT_CHARSET))
-                result.append(formataddr((nm, str(addr))))
-            val = ', '.join(result)
-        else:
-            val = Header(val, settings.DEFAULT_CHARSET)
-    else:
-        if name.lower() == 'subject':
-            val = Header(val)
-    return name, val
-
-class SafeMIMEText(MIMEText):
-    def __setitem__(self, name, val):
-        name, val = forbid_multi_line_headers(name, val)
-        MIMEText.__setitem__(self, name, val)
-
-class SafeMIMEMultipart(MIMEMultipart):
-    def __setitem__(self, name, val):
-        name, val = forbid_multi_line_headers(name, val)
-        MIMEMultipart.__setitem__(self, name, val)
-
-class SMTPConnection(object):
-    """
-    A wrapper that manages the SMTP network connection.
-    """
-
-    def __init__(self, host=None, port=None, username=None, password=None,
-                 use_tls=None, fail_silently=False):
-        self.host = host or settings.EMAIL_HOST
-        self.port = port or settings.EMAIL_PORT
-        self.username = username or settings.EMAIL_HOST_USER
-        self.password = password or settings.EMAIL_HOST_PASSWORD
-        self.use_tls = (use_tls is not None) and use_tls or settings.EMAIL_USE_TLS
-        self.fail_silently = fail_silently
-        self.connection = None
-
-    def open(self):
-        """
-        Ensures we have a connection to the email server. Returns whether or
-        not a new connection was required (True or False).
-        """
-        if self.connection:
-            # Nothing to do if the connection is already open.
-            return False
-        try:
-            # If local_hostname is not specified, socket.getfqdn() gets used.
-            # For performance, we use the cached FQDN for local_hostname.
-            self.connection = smtplib.SMTP(self.host, self.port,
-                                           local_hostname=DNS_NAME.get_fqdn())
-            if self.use_tls:
-                self.connection.ehlo()
-                self.connection.starttls()
-                self.connection.ehlo()
-            if self.username and self.password:
-                self.connection.login(self.username, self.password)
-            return True
-        except:
-            if not self.fail_silently:
-                raise
-
-    def close(self):
-        """Closes the connection to the email server."""
-        try:
-            try:
-                self.connection.quit()
-            except socket.sslerror:
-                # This happens when calling quit() on a TLS connection
-                # sometimes.
-                self.connection.close()
-            except:
-                if self.fail_silently:
-                    return
-                raise
-        finally:
-            self.connection = None
-
-    def send_messages(self, email_messages):
-        """
-        Sends one or more EmailMessage objects and returns the number of email
-        messages sent.
-        """
-        if not email_messages:
-            return
-        new_conn_created = self.open()
-        if not self.connection:
-            # We failed silently on open(). Trying to send would be pointless.
-            return
-        num_sent = 0
-        for message in email_messages:
-            sent = self._send(message)
-            if sent:
-                num_sent += 1
-        if new_conn_created:
-            self.close()
-        return num_sent
-
-    def _send(self, email_message):
-        """A helper method that does the actual sending."""
-        if not email_message.recipients():
-            return False
-        try:
-            self.connection.sendmail(email_message.from_email,
-                    email_message.recipients(),
-                    email_message.message().as_string())
-        except:
-            if not self.fail_silently:
-                raise
-            return False
-        return True
-
-class EmailMessage(object):
-    """
-    A container for email information.
-    """
-    content_subtype = 'plain'
-    mixed_subtype = 'mixed'
-    encoding = None     # None => use settings default
-
-    def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
-            connection=None, attachments=None, headers=None):
-        """
-        Initialize a single email message (which can be sent to multiple
-        recipients).
-
-        All strings used to create the message can be unicode strings (or UTF-8
-        bytestrings). The SafeMIMEText class will handle any necessary encoding
-        conversions.
-        """
-        if to:
-            assert not isinstance(to, basestring), '"to" argument must be a list or tuple'
-            self.to = list(to)
-        else:
-            self.to = []
-        if bcc:
-            assert not isinstance(bcc, basestring), '"bcc" argument must be a list or tuple'
-            self.bcc = list(bcc)
-        else:
-            self.bcc = []
-        self.from_email = from_email or settings.DEFAULT_FROM_EMAIL
-        self.subject = subject
-        self.body = body
-        self.attachments = attachments or []
-        self.extra_headers = headers or {}
-        self.connection = connection
-
-    def get_connection(self, fail_silently=False):
-        if not self.connection:
-            self.connection = SMTPConnection(fail_silently=fail_silently)
-        return self.connection
-
-    def message(self):
-        encoding = self.encoding or settings.DEFAULT_CHARSET
-        msg = SafeMIMEText(smart_str(self.body, settings.DEFAULT_CHARSET),
-                           self.content_subtype, encoding)
-        msg = self._create_message(msg)
-        msg['Subject'] = self.subject
-        msg['From'] = self.extra_headers.pop('From', self.from_email)
-        msg['To'] = ', '.join(self.to)
-
-        # Email header names are case-insensitive (RFC 2045), so we have to
-        # accommodate that when doing comparisons.
-        header_names = [key.lower() for key in self.extra_headers]
-        if 'date' not in header_names:
-            msg['Date'] = formatdate()
-        if 'message-id' not in header_names:
-            msg['Message-ID'] = make_msgid()
-        for name, value in self.extra_headers.items():
-            msg[name] = value
-        return msg
-
-    def recipients(self):
-        """
-        Returns a list of all recipients of the email (includes direct
-        addressees as well as Bcc entries).
-        """
-        return self.to + self.bcc
-
-    def send(self, fail_silently=False):
-        """Sends the email message."""
-        if not self.recipients():
-            # Don't bother creating the network connection if there's nobody to
-            # send to.
-            return 0
-        return self.get_connection(fail_silently).send_messages([self])
-
-    def attach(self, filename=None, content=None, mimetype=None):
-        """
-        Attaches a file with the given filename and content. The filename can
-        be omitted and the mimetype is guessed, if not provided.
-
-        If the first parameter is a MIMEBase subclass it is inserted directly
-        into the resulting message attachments.
-        """
-        if isinstance(filename, MIMEBase):
-            assert content == mimetype == None
-            self.attachments.append(filename)
-        else:
-            assert content is not None
-            self.attachments.append((filename, content, mimetype))
-
-    def attach_file(self, path, mimetype=None):
-        """Attaches a file from the filesystem."""
-        filename = os.path.basename(path)
-        content = open(path, 'rb').read()
-        self.attach(filename, content, mimetype)
-
-    def _create_message(self, msg):
-        return self._create_attachments(msg)
-
-    def _create_attachments(self, msg):
-        if self.attachments:
-            body_msg = msg
-            msg = SafeMIMEMultipart(_subtype=self.mixed_subtype)
-            if self.body:
-                msg.attach(body_msg)
-            for attachment in self.attachments:
-                if isinstance(attachment, MIMEBase):
-                    msg.attach(attachment)
-                else:
-                    msg.attach(self._create_attachment(*attachment))
-        return msg
-
-    def _create_mime_attachment(self, content, mimetype):
-        """
-        Converts the content, mimetype pair into a MIME attachment object.
-        """
-        basetype, subtype = mimetype.split('/', 1)
-        if basetype == 'text':
-            attachment = SafeMIMEText(smart_str(content,
-                settings.DEFAULT_CHARSET), subtype, settings.DEFAULT_CHARSET)
-        else:
-            # Encode non-text attachments with base64.
-            attachment = MIMEBase(basetype, subtype)
-            attachment.set_payload(content)
-            Encoders.encode_base64(attachment)
-        return attachment
-
-    def _create_attachment(self, filename, content, mimetype=None):
-        """
-        Converts the filename, content, mimetype triple into a MIME attachment
-        object.
-        """
-        if mimetype is None:
-            mimetype, _ = mimetypes.guess_type(filename)
-            if mimetype is None:
-                mimetype = DEFAULT_ATTACHMENT_MIME_TYPE
-        attachment = self._create_mime_attachment(content, mimetype)
-        if filename:
-            attachment.add_header('Content-Disposition', 'attachment',
-                                  filename=filename)
-        return attachment
-
-class EmailMultiAlternatives(EmailMessage):
-    """
-    A version of EmailMessage that makes it easy to send multipart/alternative
-    messages. For example, including text and HTML versions of the text is
-    made easier.
-    """
-    alternative_subtype = 'alternative'
-
-    def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
-            connection=None, attachments=None, headers=None, alternatives=None):
-        """
-        Initialize a single email message (which can be sent to multiple
-        recipients).
-
-        All strings used to create the message can be unicode strings (or UTF-8
-        bytestrings). The SafeMIMEText class will handle any necessary encoding
-        conversions.
-        """
-        super(EmailMultiAlternatives, self).__init__(subject, body, from_email, to, bcc, connection, attachments, headers)
-        self.alternatives=alternatives or []
-
-    def attach_alternative(self, content, mimetype):
-        """Attach an alternative content representation."""
-        assert content is not None
-        assert mimetype is not None
-        self.alternatives.append((content, mimetype))
-
-    def _create_message(self, msg):
-        return self._create_attachments(self._create_alternatives(msg))
-
-    def _create_alternatives(self, msg):
-        if self.alternatives:
-            body_msg = msg
-            msg = SafeMIMEMultipart(_subtype=self.alternative_subtype)
-            if self.body:
-                msg.attach(body_msg)
-            for alternative in self.alternatives:
-                msg.attach(self._create_mime_attachment(*alternative))
-        return msg
-
-def send_mail(subject, message, from_email, recipient_list,
-              fail_silently=False, auth_user=None, auth_password=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.
-
-    If auth_user is None, the EMAIL_HOST_USER setting is used.
-    If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
-
-    Note: The API for this method is frozen. New code wanting to extend the
-    functionality should use the EmailMessage class directly.
-    """
-    connection = SMTPConnection(username=auth_user, password=auth_password,
-                                fail_silently=fail_silently)
-    return EmailMessage(subject, message, from_email, recipient_list,
-                        connection=connection).send()
-
-def send_mass_mail(datatuple, fail_silently=False, auth_user=None,
-                   auth_password=None):
-    """
-    Given a datatuple of (subject, message, from_email, recipient_list), sends
-    each message to each recipient list. Returns the number of e-mails sent.
-
-    If from_email is None, the DEFAULT_FROM_EMAIL setting is used.
-    If auth_user and auth_password are set, they're used to log in.
-    If auth_user is None, the EMAIL_HOST_USER setting is used.
-    If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
-
-    Note: The API for this method is frozen. New code wanting to extend the
-    functionality should use the EmailMessage class directly.
-    """
-    connection = SMTPConnection(username=auth_user, password=auth_password,
-                                fail_silently=fail_silently)
-    messages = [EmailMessage(subject, message, sender, recipient)
-                for subject, message, sender, recipient in datatuple]
-    return connection.send_messages(messages)
-
-def mail_admins(subject, message, fail_silently=False):
-    """Sends a message to the admins, as defined by the ADMINS setting."""
-    if not settings.ADMINS:
-        return
-    EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
-                 settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS]
-                 ).send(fail_silently=fail_silently)
-
-def mail_managers(subject, message, fail_silently=False):
-    """Sends a message to the managers, as defined by the MANAGERS setting."""
-    if not settings.MANAGERS:
-        return
-    EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
-                 settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS]
-                 ).send(fail_silently=fail_silently)
diff -r 4e009aed3ca6 django/core/mail/__init__.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/core/mail/__init__.py	Wed Sep 02 12:05:20 2009 +0200
@@ -0,0 +1,115 @@
+"""
+Tools for sending email.
+"""
+
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.core.mail.message import EmailMessage, EmailMultiAlternatives
+from django.utils.importlib import import_module
+
+# Imported for backwards compatibility
+from django.core.mail.backends.smtp import EmailBackend as _SMTPConnection
+
+
+# Shortcuts for builtin email backends. The dictionary maps backend names
+# to module names. If settings.EMAIL_BACKEND isn't in this dictionary
+# it's treated as a Python import path.
+BACKENDS = {
+    'console': 'console',
+    'dummy': 'dummy',
+    'file': 'filebased',
+    'locmem': 'locmem',
+    'smtp': 'smtp',
+    }
+
+
+def get_connection(backend=None, fail_silently=False, **kwds):
+    """Load an e-mail backend and return an instance of it.
+
+    If backend is None (default) settings.EMAIL_BACKEND is used.
+
+    Both fail_silently and other keyword arguments are used in the
+    constructor of the backend.
+    """
+    path = backend or settings.EMAIL_BACKEND
+    if path in BACKENDS:
+        path = 'django.core.mail.backends.%s' % BACKENDS.get(path)
+    try:
+        mod = import_module(path)
+    except ImportError, e:
+        raise ImproperlyConfigured(('Error importing email backend %s: "%s"'
+                                    % (path, e)))
+    try:
+        cls = getattr(mod, 'EmailBackend')
+    except AttributeError:
+        raise ImproperlyConfigured(('Module "%s" does not define a '
+                                    '"EmailBackend" class' % path))
+    return cls(fail_silently=fail_silently, **kwds)
+
+
+def send_mail(subject, message, from_email, recipient_list,
+              fail_silently=False, auth_user=None, auth_password=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.
+
+    If auth_user is None, the EMAIL_HOST_USER setting is used.
+    If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
+
+    Note: The API for this method is frozen. New code wanting to extend the
+    functionality should use the EmailMessage class directly.
+    """
+    connection = get_connection(username=auth_user, password=auth_password,
+                                fail_silently=fail_silently)
+    return EmailMessage(subject, message, from_email, recipient_list,
+                        connection=connection).send()
+
+
+def send_mass_mail(datatuple, fail_silently=False, auth_user=None,
+                   auth_password=None):
+    """
+    Given a datatuple of (subject, message, from_email, recipient_list), sends
+    each message to each recipient list. Returns the number of e-mails sent.
+
+    If from_email is None, the DEFAULT_FROM_EMAIL setting is used.
+    If auth_user and auth_password are set, they're used to log in.
+    If auth_user is None, the EMAIL_HOST_USER setting is used.
+    If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
+
+    Note: The API for this method is frozen. New code wanting to extend the
+    functionality should use the EmailMessage class directly.
+    """
+    connection = get_connection(username=auth_user, password=auth_password,
+                                fail_silently=fail_silently)
+    messages = [EmailMessage(subject, message, sender, recipient)
+                for subject, message, sender, recipient in datatuple]
+    return connection.send_messages(messages)
+
+
+def mail_admins(subject, message, fail_silently=False):
+    """Sends a message to the admins, as defined by the ADMINS setting."""
+    if not settings.ADMINS:
+        return
+    EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
+                 settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS]
+                 ).send(fail_silently=fail_silently)
+
+
+def mail_managers(subject, message, fail_silently=False):
+    """Sends a message to the managers, as defined by the MANAGERS setting."""
+    if not settings.MANAGERS:
+        return
+    EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
+                 settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS]
+                 ).send(fail_silently=fail_silently)
+
+
+class SMTPConnection(_SMTPConnection):
+
+    def __init__(self, *args, **kwds):
+        import warnings
+        warnings.warn(
+            'mail.SMTPConnection is deprectated; use mail.get_connection() instead.',
+            DeprecationWarning
+        )
+        super(SMTPConnection, self).__init__(*args, **kwds)
diff -r 4e009aed3ca6 django/core/mail/backends/__init__.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/core/mail/backends/__init__.py	Wed Sep 02 12:05:20 2009 +0200
@@ -0,0 +1,1 @@
+# Mail backends shipped with Django.
diff -r 4e009aed3ca6 django/core/mail/backends/base.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/core/mail/backends/base.py	Wed Sep 02 12:05:20 2009 +0200
@@ -0,0 +1,41 @@
+"""Base email backend class."""
+
+
+class BaseEmailBackend(object):
+    """
+    Base class for email backend implementations.
+
+    Subclasses must at least overwrite send_messages().
+    """
+
+    def __init__(self, fail_silently=False, **kwds):
+        self.fail_silently = fail_silently
+
+    def open(self):
+        """Open a network connection.
+
+        This method can be overwritten by backend implementations to
+        open a network connection.
+
+        It's up to the backend implementation to track the status of
+        a network connection if it's needed by the backend.
+
+        This method can be called by applications to force a single
+        network connection to be used when sending mails. See the
+        send_messages() method of the SMTP backend for a reference
+        implementation.
+
+        The default implementation does nothing.
+        """
+        pass
+
+    def close(self):
+        """Close a network connection."""
+        pass
+
+    def send_messages(self, email_messages):
+        """
+        Sends one or more EmailMessage objects and returns the number of email
+        messages sent.
+        """
+        raise NotImplementedError
diff -r 4e009aed3ca6 django/core/mail/backends/console.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/core/mail/backends/console.py	Wed Sep 02 12:05:20 2009 +0200
@@ -0,0 +1,15 @@
+"""
+Email backend that writes messages to console instead of sending them.
+"""
+
+import sys
+
+from django.core.mail.backends.base import BaseEmailBackend
+
+
+class EmailBackend(BaseEmailBackend):
+
+    def send_messages(self, email_messages):
+        for message in email_messages:
+            sys.stdout.write('%s\n' % message.message().as_string())
+        return len(email_messages)
diff -r 4e009aed3ca6 django/core/mail/backends/dummy.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/core/mail/backends/dummy.py	Wed Sep 02 12:05:20 2009 +0200
@@ -0,0 +1,12 @@
+"""
+Dummy email backend that does nothing.
+"""
+
+
+from django.core.mail.backends.base import BaseEmailBackend
+
+
+class EmailBackend(BaseEmailBackend):
+
+    def send_messages(self, email_messages):
+        return len(email_messages)
diff -r 4e009aed3ca6 django/core/mail/backends/filebased.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/core/mail/backends/filebased.py	Wed Sep 02 12:05:20 2009 +0200
@@ -0,0 +1,82 @@
+"""Email backend that writes messages to a file."""
+
+import datetime
+import os
+import threading
+
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.core.mail.backends.base import BaseEmailBackend
+
+
+class EmailBackend(BaseEmailBackend):
+
+    def __init__(self, *args, **kwds):
+        self.stream = None
+        self._lock = threading.RLock()
+        self._counter = 0
+        self._fname = None
+        if 'file_path' in kwds:
+            self.outdir = kwds.pop('file_path')
+        else:
+            self.outdir = settings.EMAIL_FILE_PATH
+        # Make sure self.outdir is a string.
+        if not isinstance(self.outdir, basestring):
+            raise ImproperlyConfigured('Path for saving emails is invalid: %r' % self.outdir)
+        self.outdir = os.path.abspath(self.outdir)
+        # Make sure that self.outdir is an directory if it exists.
+        if os.path.exists(self.outdir) and not os.path.isdir(self.outdir):
+            raise ImproperlyConfigured('Path for saving email messages exists, but is not a directory: %s' % self.outdir)
+        # Try to create it, if it not exists.
+        elif not os.path.exists(self.outdir):
+            try:
+                os.makedirs(self.outdir)
+            except OSError, err:
+                raise ImproperlyConfigured('Could not create directory for saving email messages: %s (%s)' % (self.outdir, err))
+        # Make sure that self.outdir is writable.
+        if not os.access(self.outdir, os.W_OK):
+            raise ImproperlyConfigured('Could not write to directory: %s' % self.outdir)
+        # Finally, call super().
+        super(EmailBackend, self).__init__(*args, **kwds)
+
+    def _get_filename(self):
+        """Return a unique file name."""
+        if self._fname is None:
+            timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
+            fname = "%s-%s.log" % (timestamp, abs(id(self)))
+            self._fname = os.path.join(self.outdir, fname)
+        return self._fname
+
+    def open(self):
+        if self.stream is None:
+            self.stream = open(self._get_filename(), 'a')
+            return True
+        return False
+
+    def close(self):
+        try:
+            if self.stream is not None:
+                self.stream.close()
+        finally:
+            self.stream = None
+
+    def send_messages(self, email_messages):
+        """Write all messages to the file in a thread-safe way."""
+        if not email_messages:
+            return
+        self._lock.acquire()
+        try:
+            stream_created = self.open()
+            for message in email_messages:
+                self.stream.write('%s\n' % message.message().as_string())
+                self.stream.write('-'*79)
+                self.stream.write('\n')
+                self.stream.flush()  # flush after each message
+            if stream_created:
+                self.close()
+        except:
+            if not self.fail_silently:
+                raise
+        finally:
+            self._lock.release()
+        return len(email_messages)
diff -r 4e009aed3ca6 django/core/mail/backends/locmem.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/core/mail/backends/locmem.py	Wed Sep 02 12:05:20 2009 +0200
@@ -0,0 +1,26 @@
+"""
+Backend for test environment.
+"""
+
+from django.core import mail
+from django.core.mail.backends.base import BaseEmailBackend
+
+
+class EmailBackend(BaseEmailBackend):
+    """A email backend for use during test sessions.
+
+    The test connection stores email messages in a dummy outbox,
+    rather than sending them out on the wire.
+
+    The dummy outbox is accessible through the outbox instance attribute.
+    """
+
+    def __init__(self, *args, **kwds):
+        super(EmailBackend, self).__init__(*args, **kwds)
+        if not hasattr(mail, 'outbox'):
+            mail.outbox = []
+
+    def send_messages(self, messages):
+        """Redirect messages to the dummy outbox"""
+        mail.outbox.extend(messages)
+        return len(messages)
diff -r 4e009aed3ca6 django/core/mail/backends/smtp.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/core/mail/backends/smtp.py	Wed Sep 02 12:05:20 2009 +0200
@@ -0,0 +1,105 @@
+"""SMTP email backend class."""
+
+import smtplib
+import socket
+import threading
+
+from django.conf import settings
+from django.core.mail.backends.base import BaseEmailBackend
+from django.core.mail.utils import DNS_NAME
+
+
+class EmailBackend(BaseEmailBackend):
+    """
+    A wrapper that manages the SMTP network connection.
+    """
+
+    def __init__(self, host=None, port=None, username=None, password=None,
+                 use_tls=None, fail_silently=False, **kwds):
+        super(EmailBackend, self).__init__(fail_silently=fail_silently)
+        self.host = host or settings.EMAIL_HOST
+        self.port = port or settings.EMAIL_PORT
+        self.username = username or settings.EMAIL_HOST_USER
+        self.password = password or settings.EMAIL_HOST_PASSWORD
+        self.use_tls = (use_tls is not None) and use_tls or settings.EMAIL_USE_TLS
+        self.connection = None
+        self._lock = threading.RLock()
+
+    def open(self):
+        """
+        Ensures we have a connection to the email server. Returns whether or
+        not a new connection was required (True or False).
+        """
+        if self.connection:
+            # Nothing to do if the connection is already open.
+            return False
+        try:
+            # If local_hostname is not specified, socket.getfqdn() gets used.
+            # For performance, we use the cached FQDN for local_hostname.
+            self.connection = smtplib.SMTP(self.host, self.port,
+                                           local_hostname=DNS_NAME.get_fqdn())
+            if self.use_tls:
+                self.connection.ehlo()
+                self.connection.starttls()
+                self.connection.ehlo()
+            if self.username and self.password:
+                self.connection.login(self.username, self.password)
+            return True
+        except:
+            if not self.fail_silently:
+                raise
+
+    def close(self):
+        """Closes the connection to the email server."""
+        try:
+            try:
+                self.connection.quit()
+            except socket.sslerror:
+                # This happens when calling quit() on a TLS connection
+                # sometimes.
+                self.connection.close()
+            except:
+                if self.fail_silently:
+                    return
+                raise
+        finally:
+            self.connection = None
+
+    def send_messages(self, email_messages):
+        """
+        Sends one or more EmailMessage objects and returns the number of email
+        messages sent.
+        """
+        if not email_messages:
+            return
+        self._lock.acquire()
+        try:
+            new_conn_created = self.open()
+            if not self.connection:
+                # We failed silently on open().
+                # Trying to send would be pointless.
+                return
+            num_sent = 0
+            for message in email_messages:
+                sent = self._send(message)
+                if sent:
+                    num_sent += 1
+            if new_conn_created:
+                self.close()
+        finally:
+            self._lock.release()
+        return num_sent
+
+    def _send(self, email_message):
+        """A helper method that does the actual sending."""
+        if not email_message.recipients():
+            return False
+        try:
+            self.connection.sendmail(email_message.from_email,
+                    email_message.recipients(),
+                    email_message.message().as_string())
+        except:
+            if not self.fail_silently:
+                raise
+            return False
+        return True
diff -r 4e009aed3ca6 django/core/mail/message.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/core/mail/message.py	Wed Sep 02 12:05:20 2009 +0200
@@ -0,0 +1,274 @@
+import mimetypes
+import os
+import random
+import time
+from email import Charset, Encoders
+from email.MIMEText import MIMEText
+from email.MIMEMultipart import MIMEMultipart
+from email.MIMEBase import MIMEBase
+from email.Header import Header
+from email.Utils import formatdate, parseaddr, formataddr
+
+from django.conf import settings
+from django.core.mail.utils import DNS_NAME
+from django.utils.encoding import smart_str, force_unicode
+
+# Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from
+# some spam filters.
+Charset.add_charset('utf-8', Charset.SHORTEST, Charset.QP, 'utf-8')
+
+# Default MIME type to use on attachments (if it is not explicitly given
+# and cannot be guessed).
+DEFAULT_ATTACHMENT_MIME_TYPE = 'application/octet-stream'
+
+
+class BadHeaderError(ValueError):
+    pass
+
+
+# Copied from Python standard library, with the following modifications:
+# * Used cached hostname for performance.
+# * Added try/except to support lack of getpid() in Jython (#5496).
+def make_msgid(idstring=None):
+    """Returns a string suitable for RFC 2822 compliant Message-ID, e.g:
+
+    <20020201195627.33539.96671@nightshade.la.mastaler.com>
+
+    Optional idstring if given is a string used to strengthen the
+    uniqueness of the message id.
+    """
+    timeval = time.time()
+    utcdate = time.strftime('%Y%m%d%H%M%S', time.gmtime(timeval))
+    try:
+        pid = os.getpid()
+    except AttributeError:
+        # No getpid() in Jython, for example.
+        pid = 1
+    randint = random.randrange(100000)
+    if idstring is None:
+        idstring = ''
+    else:
+        idstring = '.' + idstring
+    idhost = DNS_NAME
+    msgid = '<%s.%s.%s%s@%s>' % (utcdate, pid, randint, idstring, idhost)
+    return msgid
+
+
+def forbid_multi_line_headers(name, val):
+    """Forbids multi-line headers, to prevent header injection."""
+    val = force_unicode(val)
+    if '\n' in val or '\r' in val:
+        raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name))
+    try:
+        val = val.encode('ascii')
+    except UnicodeEncodeError:
+        if name.lower() in ('to', 'from', 'cc'):
+            result = []
+            for item in val.split(', '):
+                nm, addr = parseaddr(item)
+                nm = str(Header(nm, settings.DEFAULT_CHARSET))
+                result.append(formataddr((nm, str(addr))))
+            val = ', '.join(result)
+        else:
+            val = Header(val, settings.DEFAULT_CHARSET)
+    else:
+        if name.lower() == 'subject':
+            val = Header(val)
+    return name, val
+
+
+class SafeMIMEText(MIMEText):
+    def __setitem__(self, name, val):
+        name, val = forbid_multi_line_headers(name, val)
+        MIMEText.__setitem__(self, name, val)
+
+
+class SafeMIMEMultipart(MIMEMultipart):
+    def __setitem__(self, name, val):
+        name, val = forbid_multi_line_headers(name, val)
+        MIMEMultipart.__setitem__(self, name, val)
+
+
+class EmailMessage(object):
+    """
+    A container for email information.
+    """
+    content_subtype = 'plain'
+    mixed_subtype = 'mixed'
+    encoding = None     # None => use settings default
+
+    def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
+                 connection=None, attachments=None, headers=None):
+        """
+        Initialize a single email message (which can be sent to multiple
+        recipients).
+
+        All strings used to create the message can be unicode strings
+        (or UTF-8 bytestrings). The SafeMIMEText class will handle any
+        necessary encoding conversions.
+        """
+        if to:
+            assert not isinstance(to, basestring), '"to" argument must be a list or tuple'
+            self.to = list(to)
+        else:
+            self.to = []
+        if bcc:
+            assert not isinstance(bcc, basestring), '"bcc" argument must be a list or tuple'
+            self.bcc = list(bcc)
+        else:
+            self.bcc = []
+        self.from_email = from_email or settings.DEFAULT_FROM_EMAIL
+        self.subject = subject
+        self.body = body
+        self.attachments = attachments or []
+        self.extra_headers = headers or {}
+        self.connection = connection
+
+    def get_connection(self, fail_silently=False):
+        from django.core.mail import get_connection
+        if not self.connection:
+            self.connection = get_connection(fail_silently=fail_silently)
+        return self.connection
+
+    def message(self):
+        encoding = self.encoding or settings.DEFAULT_CHARSET
+        msg = SafeMIMEText(smart_str(self.body, settings.DEFAULT_CHARSET),
+                           self.content_subtype, encoding)
+        msg = self._create_message(msg)
+        msg['Subject'] = self.subject
+        msg['From'] = self.extra_headers.pop('From', self.from_email)
+        msg['To'] = ', '.join(self.to)
+
+        # Email header names are case-insensitive (RFC 2045), so we have to
+        # accommodate that when doing comparisons.
+        header_names = [key.lower() for key in self.extra_headers]
+        if 'date' not in header_names:
+            msg['Date'] = formatdate()
+        if 'message-id' not in header_names:
+            msg['Message-ID'] = make_msgid()
+        for name, value in self.extra_headers.items():
+            msg[name] = value
+        return msg
+
+    def recipients(self):
+        """
+        Returns a list of all recipients of the email (includes direct
+        addressees as well as Bcc entries).
+        """
+        return self.to + self.bcc
+
+    def send(self, fail_silently=False):
+        """Sends the email message."""
+        if not self.recipients():
+            # Don't bother creating the network connection if there's nobody to
+            # send to.
+            return 0
+        return self.get_connection(fail_silently).send_messages([self])
+
+    def attach(self, filename=None, content=None, mimetype=None):
+        """
+        Attaches a file with the given filename and content. The filename can
+        be omitted and the mimetype is guessed, if not provided.
+
+        If the first parameter is a MIMEBase subclass it is inserted directly
+        into the resulting message attachments.
+        """
+        if isinstance(filename, MIMEBase):
+            assert content == mimetype == None
+            self.attachments.append(filename)
+        else:
+            assert content is not None
+            self.attachments.append((filename, content, mimetype))
+
+    def attach_file(self, path, mimetype=None):
+        """Attaches a file from the filesystem."""
+        filename = os.path.basename(path)
+        content = open(path, 'rb').read()
+        self.attach(filename, content, mimetype)
+
+    def _create_message(self, msg):
+        return self._create_attachments(msg)
+
+    def _create_attachments(self, msg):
+        if self.attachments:
+            body_msg = msg
+            msg = SafeMIMEMultipart(_subtype=self.mixed_subtype)
+            if self.body:
+                msg.attach(body_msg)
+            for attachment in self.attachments:
+                if isinstance(attachment, MIMEBase):
+                    msg.attach(attachment)
+                else:
+                    msg.attach(self._create_attachment(*attachment))
+        return msg
+
+    def _create_mime_attachment(self, content, mimetype):
+        """
+        Converts the content, mimetype pair into a MIME attachment object.
+        """
+        basetype, subtype = mimetype.split('/', 1)
+        if basetype == 'text':
+            attachment = SafeMIMEText(smart_str(content,
+                settings.DEFAULT_CHARSET), subtype, settings.DEFAULT_CHARSET)
+        else:
+            # Encode non-text attachments with base64.
+            attachment = MIMEBase(basetype, subtype)
+            attachment.set_payload(content)
+            Encoders.encode_base64(attachment)
+        return attachment
+
+    def _create_attachment(self, filename, content, mimetype=None):
+        """
+        Converts the filename, content, mimetype triple into a MIME attachment
+        object.
+        """
+        if mimetype is None:
+            mimetype, _ = mimetypes.guess_type(filename)
+            if mimetype is None:
+                mimetype = DEFAULT_ATTACHMENT_MIME_TYPE
+        attachment = self._create_mime_attachment(content, mimetype)
+        if filename:
+            attachment.add_header('Content-Disposition', 'attachment',
+                                  filename=filename)
+        return attachment
+
+
+class EmailMultiAlternatives(EmailMessage):
+    """
+    A version of EmailMessage that makes it easy to send multipart/alternative
+    messages. For example, including text and HTML versions of the text is
+    made easier.
+    """
+    alternative_subtype = 'alternative'
+
+    def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
+            connection=None, attachments=None, headers=None, alternatives=None):
+        """
+        Initialize a single email message (which can be sent to multiple
+        recipients).
+
+        All strings used to create the message can be unicode strings (or UTF-8
+        bytestrings). The SafeMIMEText class will handle any necessary encoding
+        conversions.
+        """
+        super(EmailMultiAlternatives, self).__init__(subject, body, from_email, to, bcc, connection, attachments, headers)
+        self.alternatives=alternatives or []
+
+    def attach_alternative(self, content, mimetype):
+        """Attach an alternative content representation."""
+        assert content is not None
+        assert mimetype is not None
+        self.alternatives.append((content, mimetype))
+
+    def _create_message(self, msg):
+        return self._create_attachments(self._create_alternatives(msg))
+
+    def _create_alternatives(self, msg):
+        if self.alternatives:
+            body_msg = msg
+            msg = SafeMIMEMultipart(_subtype=self.alternative_subtype)
+            if self.body:
+                msg.attach(body_msg)
+            for alternative in self.alternatives:
+                msg.attach(self._create_mime_attachment(*alternative))
+        return msg
diff -r 4e009aed3ca6 django/core/mail/utils.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/core/mail/utils.py	Wed Sep 02 12:05:20 2009 +0200
@@ -0,0 +1,19 @@
+"""
+Email message and email sending related helper functions.
+"""
+
+import socket
+
+
+# Cache the hostname, but do it lazily: socket.getfqdn() can take a couple of
+# seconds, which slows down the restart of the server.
+class CachedDnsName(object):
+    def __str__(self):
+        return self.get_fqdn()
+
+    def get_fqdn(self):
+        if not hasattr(self, '_fqdn'):
+            self._fqdn = socket.getfqdn()
+        return self._fqdn
+
+DNS_NAME = CachedDnsName()
diff -r 4e009aed3ca6 django/test/utils.py
--- a/django/test/utils.py	Sat Aug 29 12:40:47 2009 +0000
+++ b/django/test/utils.py	Wed Sep 02 12:05:20 2009 +0200
@@ -28,39 +28,26 @@
     signals.template_rendered.send(sender=self, template=self, context=context)
     return self.nodelist.render(context)
 
-class TestSMTPConnection(object):
-    """A substitute SMTP connection for use during test sessions.
-    The test connection stores email messages in a dummy outbox,
-    rather than sending them out on the wire.
-
-    """
-    def __init__(*args, **kwargs):
-        pass
-    def open(self):
-        "Mock the SMTPConnection open() interface"
-        pass
-    def close(self):
-        "Mock the SMTPConnection close() interface"
-        pass
-    def send_messages(self, messages):
-        "Redirect messages to the dummy outbox"
-        mail.outbox.extend(messages)
-        return len(messages)
 
 def setup_test_environment():
     """Perform any global pre-test setup. This involves:
 
         - Installing the instrumented test renderer
-        - Diverting the email sending functions to a test buffer
+        - Replacing the email backend with the email backend used for testing.
         - Setting the active locale to match the LANGUAGE_CODE setting.
     """
+    from django.core.mail.backends.locmem import EmailBackend
     Template.original_render = Template.render
     Template.render = instrumented_test_render
 
-    mail.original_SMTPConnection = mail.SMTPConnection
-    mail.SMTPConnection = TestSMTPConnection
-
-    mail.outbox = []
+    mail.original_email_backend = settings.EMAIL_BACKEND
+    settings.EMAIL_BACKEND = 'locmem'
+    mail.original_get_connection = mail.get_connection
+    mail.get_connection = lambda *a, **k: EmailBackend()
+    mail.original_smtp_connection = mail.SMTPConnection
+    mail.SMTPConnection = lambda *a, **k: EmailBackend()
+    if not hasattr(mail, 'outbox'):
+        mail.outbox = []
 
     deactivate()
 
@@ -74,11 +61,15 @@
     Template.render = Template.original_render
     del Template.original_render
 
-    mail.SMTPConnection = mail.original_SMTPConnection
-    del mail.original_SMTPConnection
+    settings.EMAIL_BACKEND = mail.original_email_backend
+    del mail.original_email_backend
+    mail.get_connection = mail.original_get_connection
+    del mail.original_get_connection
+    mail.SMTPConnection = mail.original_smtp_connection
+    del mail.original_smtp_connection
 
-    del mail.outbox
-
+    if hasattr(mail, 'outbox'):
+        mail.outbox = []
 
 def get_runner(settings):
     test_path = settings.TEST_RUNNER.split('.')
diff -r 4e009aed3ca6 docs/ref/settings.txt
--- a/docs/ref/settings.txt	Sat Aug 29 12:40:47 2009 +0000
+++ b/docs/ref/settings.txt	Wed Sep 02 12:05:20 2009 +0200
@@ -379,6 +379,29 @@
 This is only used if ``CommonMiddleware`` is installed (see
 :ref:`topics-http-middleware`).
 
+.. setting:: EMAIL_BACKEND
+
+EMAIL_BACKEND
+-------------
+
+.. versionadded:: 1.2
+
+Default: ``'smtp'``
+
+The backend to use for sending emails. For available backend see
+:ref:`topics-email`.
+
+.. setting:: EMAIL_FILE_PATH
+
+EMAIL_FILE_PATH
+---------------
+
+.. versionadded:: 1.2
+
+Default: ``None``
+
+The directory to which the files are written by the 'file' email backend.
+
 .. setting:: EMAIL_HOST
 
 EMAIL_HOST
diff -r 4e009aed3ca6 docs/topics/email.txt
--- a/docs/topics/email.txt	Sat Aug 29 12:40:47 2009 +0000
+++ b/docs/topics/email.txt	Wed Sep 02 12:05:20 2009 +0200
@@ -178,21 +178,20 @@
 
 .. _emailmessage-and-smtpconnection:
 
-The EmailMessage and SMTPConnection classes
-===========================================
+The EmailMessage class
+======================
 
 .. versionadded:: 1.0
 
 Django's ``send_mail()`` and ``send_mass_mail()`` functions are actually thin
-wrappers that make use of the ``EmailMessage`` and ``SMTPConnection`` classes
-in ``django.core.mail``.  If you ever need to customize the way Django sends
-e-mail, you can subclass these two classes to suit your needs.
+wrappers that make use of the ``EmailMessage`` class in ``django.core.mail``.
+
+Not all features of the ``EmailMessage`` class are available through the
+``send_mail()`` and related wrapper functions. If you wish to use advanced
+features, such as BCC'ed recipients, file attachments, or multi-part
+e-mail, you'll need to create ``EmailMessage`` instances directly.
 
 .. note::
-    Not all features of the ``EmailMessage`` class are available through the
-    ``send_mail()`` and related wrapper functions. If you wish to use advanced
-    features, such as BCC'ed recipients, file attachments, or multi-part
-    e-mail, you'll need to create ``EmailMessage`` instances directly.
 
     This is a design feature. ``send_mail()`` and related functions were
     originally the only interface Django provided. However, the list of
@@ -201,9 +200,37 @@
     original functions only for backwards compatibility.
 
 In general, ``EmailMessage`` is responsible for creating the e-mail message
-itself. ``SMTPConnection`` is responsible for the network connection side of
-the operation. This means you can reuse the same connection (an
-``SMTPConnection`` instance) for multiple messages.
+itself. The e-mail backend is responsible for sending the e-mail. By default
+the SMTP backend is used to send e-mails using a SMTP server.
+
+If you're sending lots of messages at once, the ``send_messages()`` method of
+the e-mail backend is useful. It takes a list of ``EmailMessage``
+instances (or subclasses) and sends them over a single connection. For example,
+if you have a function called ``get_notification_email()`` that returns a
+list of ``EmailMessage`` objects representing some periodic e-mail you wish to
+send out, you could send this with::
+
+    connection = SMTPConnection()   # Use default settings for connection
+    messages = get_notification_email()
+    connection.send_messages(messages)
+
+If you want to resuse the same connection for multiple messages, but can't
+use ``send_messages()``, you'll have to use the ``open()`` and ``close()``
+methods of the e-mail backend::
+
+    from django.core import mail
+
+    connection = mail.get_connection()
+    connection.open()
+    email1 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com',
+                  	       ['to1@example.com'], connection=connection)
+    email1.send()
+    email2 = mail.EmailMessage('Hello again', 'Body goes here',
+                               'from@example.com', ['to1@example.com'],
+                               connection=connection)
+    email2.send()
+    connection.close()
+
 
 EmailMessage Objects
 --------------------
@@ -227,7 +254,7 @@
     * ``bcc``: A list or tuple of addresses used in the "Bcc" header when
       sending the e-mail.
 
-    * ``connection``: An ``SMTPConnection`` instance. Use this parameter if
+    * ``connection``: An e-mail backend instance. Use this parameter if
       you want to use the same connection for multiple messages. If omitted, a
       new connection is created when ``send()`` is called.
 
@@ -331,8 +358,55 @@
     msg.content_subtype = "html"  # Main content is now text/html
     msg.send()
 
-SMTPConnection Objects
-----------------------
+
+E-Mail Backends
+===============
+
+.. versionadded:: 1.2
+
+The actual sending of an e-mail is handled by the e-mail backend.
+By default the SMTP backend will be used.
+
+The :meth:`get_connection` function in ``django.core.mail`` returns an
+instance of the e-mail backend.
+
+.. currentmodule:: django.core.mail
+
+.. function:: get_connection(backend=None, fail_silently=False, *args, **kwargs)
+
+If ``backend`` is ``None`` an instance of the e-mail backend configured
+in your settings file is returned. Use this parameter to get a different
+backend than configured in your ``EMAIL_BACKEND`` setting.
+All other arguments are passed directly to the constructor of the
+e-mail backend.
+
+Your e-mail backend preferences goes in the ``EMAIL_BACKEND`` setting in
+your settings file. Here's an explanation of all available values for
+``EMAIL_BACKEND``.
+
+SMTP backend
+------------
+
+This is the default backend. E-mail will be sent through a SMTP server.
+The server address and authentication credentials are set in the
+:setting:`EMAIL_HOST`, :setting:`EMAIL_POST`, :setting:`EMAIL_HOST_USER`,
+:setting:`EMAIL_HOST_PASSWORD` and :setting:`EMAIL_USE_TLS` settings in your
+settings file.
+
+The SMTP backend is configured in the default settings::
+
+    EMAIL_BACKEND = 'smtp'
+
+
+SMTPConnection objects
+~~~~~~~~~~~~~~~~~~~~~~
+
+.. deprecated:: 1.2
+
+``mail.SMTPConnection`` is deprecated, use
+:meth:`~django.core.mail.get_connection` instead.
+For backwards compatibility ``SMTPConnection`` is still available in
+``django.core.mail`` as an alias for the SMTP backend.
 
 .. class:: SMTPConnection
 
@@ -340,19 +414,70 @@
 password for the SMTP server. If you don't specify one or more of those
 options, they are read from your settings file.
 
-If you're sending lots of messages at once, the ``send_messages()`` method of
-the ``SMTPConnection`` class is useful. It takes a list of ``EmailMessage``
-instances (or subclasses) and sends them over a single connection. For example,
-if you have a function called ``get_notification_email()`` that returns a
-list of ``EmailMessage`` objects representing some periodic e-mail you wish to
-send out, you could send this with::
+Console backend (for development)
+---------------------------------
 
-    connection = SMTPConnection()   # Use default settings for connection
-    messages = get_notification_email()
-    connection.send_messages(messages)
+Instead of sending out real e-mails the console backend just writes the e-mails
+that would be send to the standard output. To use this backend, use
+in your settings::
 
-Testing e-mail sending
-----------------------
+    EMAIL_BACKEND = 'console'
+
+File backend (for development)
+------------------------------
+
+The file backend writes e-mails to a file. A new file is created for each
+new instance of this backend. The directory to which the files are written
+is either taken from the :setting:`EMAIL_FILE_PATH` setting or from the
+``file_path`` keyword when creating a connection with ``get_connection()``.
+To use this backend, use in your settings::
+
+    EMAIL_BACKEND = 'file'
+    EMAIL_FILE_PATH = '/tmp/app-messages' # change this to a proper location
+
+In-memory backend (for development and testing)
+-----------------------------------------------
+
+The ``'locmem'`` backend stores messages in a special attribute of the
+``django.core.mail`` module. The ``outbox`` attribute is created when the
+first message is send. It's a list with an ``EmailMessage`` instance for
+each message that would be send. To use this backend, use in your settings::
+
+  EMAIL_BACKEND = 'locmem'
+
+This backend is used as a drop-in replacement for the original e-mail
+backend configured in your settings file by the
+:ref:`testing framework <topics-testing-email>`.
+
+Dummy backend (for development)
+-------------------------------
+
+As the name suggests the dummy backend does nothing with your messages.
+To enable this backend, use in your settings file::
+
+   EMAIL_BACKEND = 'dummy'
+
+This backend can be useful during development, if you even want to suppress
+the output produced by the console backend.
+
+Using a custom e-mail backend
+-----------------------------
+
+If you need to change how e-mails are send you can write your own e-mail
+backend. The ``EMAIL_BACKEND`` setting in your settings file is then the
+Python import path for your backend.
+
+Custom e-mail backends should subclass ``BaseEmailBackend`` that is located
+in the ``django.core.mail.backends.base`` module. A custom e-mail
+backend must implement the ``send_messages(email_messages)`` method. This
+method receives a list of ``EMailMessage`` instances and returns the number
+of successfully delivered messages. If your backend should be capable of
+network connection handling, you should implement both the ``open()``
+and ``close()`` methods too. Refer to ``SMTPEmailBackend`` for a reference
+implementation.
+
+Testing E-mail Sending
+======================
 
 The are times when you do not want Django to send e-mails at all. For example,
 while developing a website, you probably don't want to send out thousands of
@@ -360,16 +485,20 @@
 people under the right conditions, and that those e-mails will contain the
 correct content.
 
-The easiest way to test your project's use of e-mail is to use a "dumb" e-mail
-server that receives the e-mails locally and displays them to the terminal,
-but does not actually send anything. Python has a built-in way to accomplish
-this with a single command::
+The easiest way to test your project's use of e-mail is to change the
+``EMAIL_BACKEND`` setting in your settings file to ``'console'`` to use
+a backend that doesn't send any e-mail but writes them to the
+terminal.
+
+Alternatively use a "dumb" e-mail server that receives the e-mails locally
+and displays them to the terminal, but does not actually send anything.
+Python has a built-in way to accomplish this with a single command::
 
     python -m smtpd -n -c DebuggingServer localhost:1025
 
 This command will start a simple SMTP server listening on port 1025 of
-localhost. This server simply prints to standard output all email headers and
-the email body. You then only need to set the :setting:`EMAIL_HOST` and
+localhost. This server simply prints to standard output all e-mail headers and
+the e-mail body. You then only need to set the :setting:`EMAIL_HOST` and
 :setting:`EMAIL_PORT` accordingly, and you are set.
 
 For more entailed testing and processing of e-mails locally, see the Python
diff -r 4e009aed3ca6 docs/topics/testing.txt
--- a/docs/topics/testing.txt	Sat Aug 29 12:40:47 2009 +0000
+++ b/docs/topics/testing.txt	Wed Sep 02 12:05:20 2009 +0200
@@ -1104,6 +1104,8 @@
     ``target_status_code`` will be the url and status code for the final
     point of the redirect chain.
 
+.. _topics-testing-email:
+
 E-mail services
 ---------------
 
@@ -1117,7 +1119,7 @@
 contents of each message -- without actually sending the messages.
 
 The test runner accomplishes this by transparently replacing the normal
-:class:`~django.core.mail.SMTPConnection` class with a different version.
+email backend with a testing backend.
 (Don't worry -- this has no effect on any other e-mail senders outside of
 Django, such as your machine's mail server, if you're running one.)
 
@@ -1129,9 +1131,8 @@
 ``django.core.mail.outbox``. This is a simple list of all
 :class:`<~django.core.mail.EmailMessage>` instances that have been sent.
 It does not exist under normal execution conditions, i.e., when you're not
-running unit tests. The outbox is created during test setup, along with the
-dummy :class:`<~django.core.mail.SMTPConnection>`. When the test framework is
-torn down, the standard :class:`<~django.core.mail.SMTPConnection>` class is
+running unit tests. The outbox is created during test setup.
+When the test framework is torn down, the standard mail backend
 restored, and the test outbox is destroyed.
 
 The ``outbox`` attribute is a special attribute that is created *only* when
diff -r 4e009aed3ca6 tests/regressiontests/mail/custombackend.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/regressiontests/mail/custombackend.py	Wed Sep 02 12:05:20 2009 +0200
@@ -0,0 +1,15 @@
+"""A custom backend for testing."""
+
+from django.core.mail.backends.base import BaseEmailBackend
+
+
+class EmailBackend(BaseEmailBackend):
+
+    def __init__(self, *args, **kwargs):
+        super(EmailBackend, self).__init__(*args, **kwargs)
+        self.test_outbox = []
+
+    def send_messages(self, email_messages):
+        # Messages are stored in a instance variable for testing.
+        self.test_outbox.extend(email_messages)
+        return len(email_messages)
diff -r 4e009aed3ca6 tests/regressiontests/mail/tests.py
--- a/tests/regressiontests/mail/tests.py	Sat Aug 29 12:40:47 2009 +0000
+++ b/tests/regressiontests/mail/tests.py	Wed Sep 02 12:05:20 2009 +0200
@@ -1,10 +1,16 @@
 # coding: utf-8
+
 r"""
 # Tests for the django.core.mail.
 
+>>> import os
+>>> import shutil
+>>> import tempfile
 >>> from django.conf import settings
 >>> from django.core import mail
 >>> from django.core.mail import EmailMessage, mail_admins, mail_managers, EmailMultiAlternatives
+>>> from django.core.mail.backends.base import BaseEmailBackend
+>>> from django.core.mail.backends import console, dummy, locmem, filebased, smtp
 >>> from django.utils.translation import ugettext_lazy
 
 # Test normal ascii character case:
@@ -138,4 +144,128 @@
 JVBERi0xLjQuJS4uLg==
 ...
 
+# Make sure that the console backend writes to stdout
+>>> connection = console.EmailBackend()
+>>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
+>>> connection.send_messages([email])
+Content-Type: text/plain; charset="utf-8"
+MIME-Version: 1.0
+Content-Transfer-Encoding: quoted-printable
+Subject: Subject
+From: from@example.com
+To: to@example.com
+Date: ...
+Message-ID: ...
+
+Content
+...
+1
+
+# Make sure that dummy backends returns correct number of sent messages
+>>> connection = dummy.EmailBackend()
+>>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
+>>> connection.send_messages([email, email, email])
+3
+
+# Make sure that locmen backend populates the outbox
+>>> mail.outbox = []
+>>> connection = locmem.EmailBackend()
+>>> email1 = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
+>>> email2 = EmailMessage('Subject 2', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
+>>> connection.send_messages([email1, email2])
+2
+>>> len(mail.outbox)
+2
+>>> mail.outbox[0].subject
+'Subject'
+>>> mail.outbox[1].subject
+'Subject 2'
+
+# Make sure that multiple locmem connections share mail.outbox
+>>> mail.outbox = []
+>>> connection1 = locmem.EmailBackend()
+>>> connection2 = locmem.EmailBackend()
+>>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
+>>> connection1.send_messages([email])
+1
+>>> connection2.send_messages([email])
+1
+>>> len(mail.outbox)
+2
+
+# Make sure that the file backend write to the right location
+>>> tmp_dir = tempfile.mkdtemp()
+>>> connection = filebased.EmailBackend(file_path=tmp_dir)
+>>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
+>>> connection.send_messages([email])
+1
+>>> len(os.listdir(tmp_dir))
+1
+>>> print open(os.path.join(tmp_dir, os.listdir(tmp_dir)[0])).read()
+Content-Type: text/plain; charset="utf-8"
+MIME-Version: 1.0
+Content-Transfer-Encoding: quoted-printable
+Subject: Subject
+From: from@example.com
+To: to@example.com
+Date: ...
+Message-ID: ...
+
+Content
+...
+>>> connection2 = filebased.EmailBackend(file_path=tmp_dir)
+>>> connection2.send_messages([email])
+1
+>>> len(os.listdir(tmp_dir))
+2
+>>> connection.send_messages([email])
+1
+>>> len(os.listdir(tmp_dir))
+2
+>>> email.connection = filebased.EmailBackend(file_path=tmp_dir)
+>>> connection_created = connection.open()
+>>> num_sent = email.send()
+>>> len(os.listdir(tmp_dir))
+3
+>>> num_sent = email.send()
+>>> len(os.listdir(tmp_dir))
+3
+>>> connection.close()
+>>> shutil.rmtree(tmp_dir)
+
+# Make sure that get_connection() accepts arbitrary keyword that might be
+# used with custom backends.
+# Note: mail.get_connection() is patched when the test environment is set
+# up, we have to use mail.original_get_connection() instead.
+>>> c = mail.original_get_connection(fail_silently=True, foo='bar')
+>>> c.fail_silently
+True
+
+# Test custom backend defined in this suite.
+>>> conn = mail.original_get_connection('regressiontests.mail.custombackend')
+>>> hasattr(conn, 'test_outbox')
+True
+>>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
+>>> conn.send_messages([email])
+1
+>>> len(conn.test_outbox)
+1
+
+# Test backend argument of mail.get_connection()
+>>> isinstance(mail.original_get_connection('smtp'), smtp.EmailBackend)
+True
+>>> isinstance(mail.original_get_connection('locmem'), locmem.EmailBackend)
+True
+>>> isinstance(mail.original_get_connection('dummy'), dummy.EmailBackend)
+True
+>>> isinstance(mail.original_get_connection('console'), console.EmailBackend)
+True
+>>> tmp_dir = tempfile.mkdtemp()
+>>> isinstance(mail.original_get_connection('file', file_path=tmp_dir), filebased.EmailBackend)
+True
+>>> shutil.rmtree(tmp_dir)
+>>> settings.EMAIL_FILE_PATH = None
+>>> isinstance(mail.get_connection(), locmem.EmailBackend)
+True
+
 """
