diff -r 1b4df7524b7c django/conf/global_settings.py
--- a/django/conf/global_settings.py	Mon Aug 24 15:45:48 2009 +0000
+++ b/django/conf/global_settings.py	Wed Aug 26 20:53:43 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 1b4df7524b7c django/core/mail.py
--- a/django/core/mail.py	Mon Aug 24 15:45:48 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 1b4df7524b7c django/core/mail/__init__.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/core/mail/__init__.py	Wed Aug 26 20:53:43 2009 +0200
@@ -0,0 +1,106 @@
+"""
+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. If settings.EMAIL_BACKEND isn't in
+# this list it's treated as a Python import path.
+BACKENDS = ('smtp', 'dummy', 'console', 'locmem', 'file')
+
+
+def get_connection(fail_silently=False, **kwds):
+    """Load configured email backend and return an instance of it.
+
+    Both fail_silently and other keyword arguments are used in the
+    constructor of the backend.
+    """
+    path = settings.EMAIL_BACKEND
+    if path in BACKENDS:
+        path = 'django.core.mail.backends.%s' % 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 1b4df7524b7c django/core/mail/backends/__init__.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/core/mail/backends/__init__.py	Wed Aug 26 20:53:43 2009 +0200
@@ -0,0 +1,1 @@
+# Mail backends shipped with Django.
diff -r 1b4df7524b7c django/core/mail/backends/base.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/core/mail/backends/base.py	Wed Aug 26 20:53:43 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 1b4df7524b7c django/core/mail/backends/console.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/core/mail/backends/console.py	Wed Aug 26 20:53:43 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 1b4df7524b7c django/core/mail/backends/dummy.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/core/mail/backends/dummy.py	Wed Aug 26 20:53:43 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 1b4df7524b7c django/core/mail/backends/file.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/core/mail/backends/file.py	Wed Aug 26 20:53:43 2009 +0200
@@ -0,0 +1,95 @@
+"""Email backend that writes messages to a file."""
+
+import os
+import sys
+import thread
+import threading
+import time
+
+try:
+    from hashlib import md5
+except ImportError:
+    from md5 import md5
+
+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
+        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 _count(self):
+        # counter is required to build a unique filename if connections
+        # are opened in a really short time.
+        if self._counter == sys.maxint-1:
+            self._counter = 0
+        self._counter += 1
+        return self._counter
+
+    def _get_filename(self):
+        """Return a unique file name."""
+        digest = md5('%d%d%s%d' % (id(self), thread.get_ident(),
+                                   time.time(), self._count())
+                     ).hexdigest()
+        return os.path.join(self.outdir, '%s-messages.log' % digest)
+
+    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 1b4df7524b7c django/core/mail/backends/locmem.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/core/mail/backends/locmem.py	Wed Aug 26 20:53:43 2009 +0200
@@ -0,0 +1,27 @@
+"""
+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)
+        self.outbox = []
+
+    def send_messages(self, messages):
+        """Redirect messages to the dummy outbox"""
+        self.outbox.extend(messages)
+        if hasattr(mail, 'outbox'):
+            mail.outbox.extend(messages)
+        return len(messages)
diff -r 1b4df7524b7c django/core/mail/backends/smtp.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/core/mail/backends/smtp.py	Wed Aug 26 20:53:43 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 1b4df7524b7c django/core/mail/message.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/core/mail/message.py	Wed Aug 26 20:53:43 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 1b4df7524b7c django/core/mail/utils.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/core/mail/utils.py	Wed Aug 26 20:53:43 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 1b4df7524b7c django/test/utils.py
--- a/django/test/utils.py	Mon Aug 24 15:45:48 2009 +0000
+++ b/django/test/utils.py	Wed Aug 26 20:53:43 2009 +0200
@@ -28,37 +28,22 @@
     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.original_email_backend = settings.EMAIL_BACKEND
+    settings.EMAIL_BACKEND = 'locmem'
+    mail.original_smtp_connection = mail.SMTPConnection
+    mail.SMTPConnection = lambda *a, **k: mail.get_connection()
 
     mail.outbox = []
 
@@ -74,8 +59,10 @@
     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.SMTPConnection = mail.original_smtp_connection
+    del mail.original_smtp_connection
 
     del mail.outbox
 
diff -r 1b4df7524b7c docs/ref/settings.txt
--- a/docs/ref/settings.txt	Mon Aug 24 15:45:48 2009 +0000
+++ b/docs/ref/settings.txt	Wed Aug 26 20:53:43 2009 +0200
@@ -379,6 +379,18 @@
 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_HOST
 
 EMAIL_HOST
diff -r 1b4df7524b7c docs/topics/email.txt
--- a/docs/topics/email.txt	Mon Aug 24 15:45:48 2009 +0000
+++ b/docs/topics/email.txt	Wed Aug 26 20:53:43 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 email backend is responsible for sending the email. By default
+the SMTP backend is used to send emails using a SMTP server.
+
+If you're sending lots of messages at once, the ``send_messages()`` method of
+the email 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 email 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 email 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,28 +358,98 @@
     msg.content_subtype = "html"  # Main content is now text/html
     msg.send()
 
-SMTPConnection Objects
-----------------------
+
+Email Backends
+==============
+
+.. versionadded:: 1.2
+
+The actual sending of an email is handled by the email backend.
+By default the SMTP backend will be used.
+
+Your email 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. Email 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
+~~~~~~~~~~~~~~~~~~~~~~
 
 .. class:: SMTPConnection
 
+.. note::
+
+   ``mail.SMTPConnection`` is deprecated, use ``mail.get_connection`` instead.
+   For backwards compatibility ``SMTPConnection`` is still available in
+   ``django.core.mail`` as an alias for the SMTP backend.
+
 The ``SMTPConnection`` class is initialized with the host, port, username and
 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 emails the console backend just writes the emails
+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 emails 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
+
+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 email backend
+----------------------------
+
+If you need to change how emails are send you can write your own email
+backend. The ``EMAIL_BACKEND`` setting in your settings file is then the
+Python import path for your backend.
+
+Custom email backends should subclass ``BaseEmailBackend`` that is located
+in the ``django.core.mail.backends.base`` module. A custom email
+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 Email 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,10 +457,14 @@
 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 email 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
 
diff -r 1b4df7524b7c docs/topics/testing.txt
--- a/docs/topics/testing.txt	Mon Aug 24 15:45:48 2009 +0000
+++ b/docs/topics/testing.txt	Wed Aug 26 20:53:43 2009 +0200
@@ -1117,7 +1117,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 +1129,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 1b4df7524b7c tests/regressiontests/mail/custombackend.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/regressiontests/mail/custombackend.py	Wed Aug 26 20:53:43 2009 +0200
@@ -0,0 +1,9 @@
+"""A custom backend for testing."""
+
+from django.core.mail.backends.base import BaseEmailBackend
+
+
+class EmailBackend(BaseEmailBackend):
+
+    def send_messages(self, email_messages):
+        return len(email_messages)
diff -r 1b4df7524b7c tests/regressiontests/mail/tests.py
--- a/tests/regressiontests/mail/tests.py	Mon Aug 24 15:45:48 2009 +0000
+++ b/tests/regressiontests/mail/tests.py	Wed Aug 26 20:53:43 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, file
 >>> from django.utils.translation import ugettext_lazy
 
 # Test normal ascii character case:
@@ -138,4 +144,111 @@
 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(connection1.outbox)
+1
+>>> len(connection2.outbox)
+1
+>>> len(mail.outbox)
+2
+
+# Make sure that the file backend write to the right location
+>>> tmp_dir = tempfile.mkdtemp()
+>>> shutil.rmtree(tmp_dir)
+>>> connection = file.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 = file.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))
+3
+>>> email.connection = connection
+>>> connection_created = connection.open()
+>>> num_sent = email.send()
+>>> len(os.listdir(tmp_dir))
+4
+>>> num_sent = email.send()
+>>> len(os.listdir(tmp_dir))
+4
+>>> connection.close()
+>>> shutil.rmtree(tmp_dir)
+
+# Make sure that get_connection() accepts arbitrary keyword that might be
+# used with custom backends.
+>>> c = mail.get_connection(fail_silently=True, foo='bar')
+>>> c.fail_silently
+True
+
+# Test custom backend defined in this suite.
+>>> settings.EMAIL_BACKEND = 'regressiontests.mail.custombackend'
+>>> conn = mail.get_connection()
+>>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
+>>> conn.send_messages([email])
+1
+>>> settings.EMAIL_BACKEND = 'locmem'
 """
