Index: tests/regressiontests/utils/tests.py
===================================================================
--- tests/regressiontests/utils/tests.py	(revision 7141)
+++ tests/regressiontests/utils/tests.py	(working copy)
@@ -8,11 +8,13 @@
 
 import timesince
 import datastructures
+import mail
 
 # Extra tests
 __test__ = {
     'timesince': timesince,
     'datastructures': datastructures,
+    'mail': mail,
 }
 
 class TestUtilsHtml(TestCase):
Index: tests/regressiontests/utils/mail.py
===================================================================
--- tests/regressiontests/utils/mail.py	(revision 0)
+++ tests/regressiontests/utils/mail.py	(revision 0)
@@ -0,0 +1,39 @@
+r"""
+>>> from django.utils.mail import wrap
+
+>>> wrap('1234 67 9', 100)
+u'1234 67 9'
+>>> wrap('1234 67 9', 9)
+u'1234 67 9'
+>>> wrap('1234 67 9', 8)
+u'1234 67 \n 9'
+
+>>> wrap('short\na long line', 7)
+u'short\na long \n line'
+
+>>> wrap('email wrapping: \nStrips spaces before lines and uses soft spacing for line breaks', 30)
+u'email wrapping:\nStrips spaces before lines  \nand uses soft spacing for  \nline breaks'
+
+>>> long_word = 'l%sng' % ('0123456789'*100)
+
+# Long word broken at 997 (998 - 1 char for space)
+>>> wrap(long_word) == '%s \n%s' % (long_word[:997], long_word[997:])
+True
+>>> out = wrap('a %s word' % long_word, 11)
+>>> '%s...%s' % (out[:20], out[-20:])
+u'a  \nl012345678901234...9012345 \n6789ng word'
+
+>>> wrap('>test me out', 10)
+u'>test me  \n> out'
+>>> wrap('test me >out', 10)
+u'test me  \n >out'
+
+>>> wrap('> one\n>> two which will wrap\n>>> three', 15)
+u'> one\n>> two which  \n>> will wrap\n>>> three'
+
+>>> wrap('preserve  whitespace good  and\n  proper\n', 9)
+u'preserve  \n  \nwhitespace \n good   \nand\n  proper\n'
+
+>>> wrap('do not touch -- \nreal signatures\n-- \nThe boss', 13)
+u'do not touch  \n--\nreal  \nsignatures\n-- \nThe boss'
+"""
Index: django/core/mail.py
===================================================================
--- django/core/mail.py	(revision 7141)
+++ django/core/mail.py	(working copy)
@@ -4,6 +4,7 @@
 
 from django.conf import settings
 from django.utils.encoding import smart_str, force_unicode
+from django.utils.mail import wrap
 from email import Charset, Encoders
 from email.MIMEText import MIMEText
 from email.MIMEMultipart import MIMEMultipart
@@ -86,6 +87,20 @@
     return name, val
 
 class SafeMIMEText(MIMEText):
+    def __init__(self, _text, _subtype='plain', *args, **kwargs):
+        format_flowed = False
+        if 'wrap_text' in kwargs:
+            if kwargs.pop('wrap_text') and _subtype == 'plain':
+                _text = wrap(_text)
+                format_flowed = True
+        if 'format_flowed' in kwargs:
+            if kwargs.pop('format_flowed') and _subtype == 'plain':
+                format_flowed = True
+        MIMEText.__init__(self, _text, _subtype, *args, **kwargs)
+        if format_flowed:
+            self.set_param('format', 'flowed', 'Content-Type')
+            self.set_param('delsp', 'yes', 'Content-Type')
+
     def __setitem__(self, name, val):
         name, val = forbid_multi_line_headers(name, val)
         MIMEText.__setitem__(self, name, val)
@@ -190,7 +205,7 @@
     encoding = None     # None => use settings default
 
     def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
-            connection=None, attachments=None, headers=None):
+            connection=None, attachments=None, headers=None, wrap_text=False):
         """
         Initialise a single email message (which can be sent to multiple
         recipients).
@@ -213,6 +228,7 @@
         self.attachments = attachments or []
         self.extra_headers = headers or {}
         self.connection = connection
+        self.wrap_text = wrap_text
 
     def get_connection(self, fail_silently=False):
         if not self.connection:
@@ -221,7 +237,9 @@
 
     def message(self):
         encoding = self.encoding or settings.DEFAULT_CHARSET
-        msg = SafeMIMEText(smart_str(self.body, settings.DEFAULT_CHARSET), self.content_subtype, encoding)
+        msg = SafeMIMEText(smart_str(self.body, settings.DEFAULT_CHARSET),
+                           self.content_subtype, encoding,
+                           wrap_text=self.wrap_text)
         if self.attachments:
             body_msg = msg
             msg = SafeMIMEMultipart(_subtype=self.multipart_subtype)
@@ -354,4 +372,3 @@
     EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message,
             settings.SERVER_EMAIL, [a[1] for a in
                 settings.MANAGERS]).send(fail_silently=fail_silently)
-
Index: django/utils/mail.py
===================================================================
--- django/utils/mail.py	(revision 0)
+++ django/utils/mail.py	(revision 0)
@@ -0,0 +1,56 @@
+import re
+from django.utils.encoding import force_unicode
+from django.utils.functional import allow_lazy
+
+def wrap(text, width=78):
+    """
+    An email based word-wrap function that preserves existing line breaks and
+    adds soft line breaks (' \n').
+
+    Long words are not wrapped, so the output text may have lines longer than
+    ``width``.
+
+    Trailing spaces are stripped from each line so they are not confused with
+    soft line breaks. All other white space is preserved.
+
+    Soft-broken lines are parsed for quotes and space-stuffed in accordance
+    with RFC 3676. A break is forced at 998 characters (RFC 2822 2.1.1).
+    """
+    text = force_unicode(text)
+    # Signature lines aren't stripped -- see RFC 3676 4.3
+    text = re.sub(r'(?m)(?<!^--) +$', '', text)
+    def _parse_line(line, quote, new_line):
+        if new_line and line.startswith('>'):
+            quote = re.match('>+', line).group()
+        elif quote:
+            line = '%s %s' % (quote, line)
+        elif (line.startswith('>') or line.startswith('From ')):
+            # Space stuff -- see RFC 3676 4.4
+            line = ' %s' % line
+        max_width = (line.endswith('\n') and width + 1 or width)
+        return line, quote, max_width
+    def _generator():
+        for line in text.splitlines(True):   # True keeps trailing linebreaks
+            quote = ''
+            line, quote, max_width = _parse_line(line, quote, new_line=True)
+            while len(line) > max_width:
+                space = line[:max_width].rfind(' ') + 1
+                if space == 0:
+                    space = line.find(' ') + 1
+                    space = min(space, 998)
+                    if space == 0:
+                        if len(line) > 998:
+                            space = 998
+                        else:
+                            yield line
+                            line = ''
+                            break
+                if space >= max_width:
+                    space -= 1
+                yield '%s \n' % line[:space]
+                line = line[space:]
+                line, quote, max_width = _parse_line(line, quote, False)
+            if line:
+                yield line
+    return u''.join(_generator())
+wrap = allow_lazy(wrap, unicode)
