Index: includes/mail.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/mail.inc,v
retrieving revision 1.25
diff -u -p -r1.25 mail.inc
--- includes/mail.inc	1 Sep 2009 17:40:27 -0000	1.25
+++ includes/mail.inc	15 Oct 2009 05:58:51 -0000
@@ -14,7 +14,7 @@
  * appropriate places in the template. Processed e-mail templates are
  * requested from hook_mail() from the module sending the e-mail. Any module
  * can modify the composed e-mail message array using hook_mail_alter().
- * Finally drupal_mail_sending_system()->mail() sends the e-mail, which can
+ * Finally drupal_mail_system()->mail() sends the e-mail, which can
  * be reused if the exact same composed e-mail is to be sent to multiple
  * recipients.
  *
@@ -78,8 +78,8 @@
  * @param $from
  *   Sets From to this value, if given.
  * @param $send
- *   Send the message directly, without calling
- *   drupal_mail_sending_system()->mail() manually.
+ *   Send the message directly, without calling drupal_mail_system()->mail()
+ *   manually.
  * @return
  *   The $message array structure containing all details of the
  *   message. If already sent ($send = TRUE), then the 'result' element
@@ -93,6 +93,8 @@ function drupal_mail($module, $key, $to,
   // Bundle up the variables into a structured array for altering.
   $message = array(
     'id'       => $module . '_' . $key,
+    'module'   => $module,
+    'key'      => $key,
     'to'       => $to,
     'from'     => isset($from) ? $from : $default_from,
     'language' => $language,
@@ -129,12 +131,15 @@ function drupal_mail($module, $key, $to,
   // Invoke hook_mail_alter() to allow all modules to alter the resulting e-mail.
   drupal_alter('mail', $message);
 
-  // Concatenate and wrap the e-mail body.
-  $message['body'] = is_array($message['body']) ? drupal_wrap_mail(implode("\n\n", $message['body'])) : drupal_wrap_mail($message['body']);
+  // Retrieve the responsible implementation for this message.
+  $system = drupal_mail_system($module, $key);
+
+  // Format the message body.
+  $message = $system->format($message);
 
   // Optionally send e-mail.
   if ($send) {
-    $message['result'] = drupal_mail_sending_system($module, $key)->mail($message);
+    $message['result'] = $system->mail($message);
 
     // Log errors
     if (!$message['result']) {
@@ -149,11 +154,23 @@ function drupal_mail($module, $key, $to,
 /**
  * Returns an object that implements the MailSystemInterface.
  *
- * Allows for one or more custom mail backends to send mail messages
+ * Allows for one or more custom mail backends to format and send mail messages
  * composed using drupal_mail().
  *
+ * An implementation needs to implement the following methods:
+ * - format: Allows to preprocess, format, and postprocess a mail
+ *   message before it is passed to the sending system. By default, all messages
+ *   may contain HTML and are converted to plain-text by the DefaultMailSystem
+ *   implementation. For example, an alternative implementation could override
+ *   the default implementation and additionally sanitize the HTML for usage in
+ *   a MIME-encoded e-mail, but still invoking the DefaultMailSystem
+ *   implementation to generate an alternate plain-text version for sending.
+ * - mail: Sends a message through a custom mail sending engine.
+ *   By default, all messages are sent via PHP's mail() function by the
+ *   DefaultMailSystem implementation.
+ *
  * The selection of a particular implementation is controlled via the variable
- * 'mail_sending_system', which is a keyed array.  The default implementation
+ * 'mail_system', which is a keyed array.  The default implementation
  * is the class whose name is the value of 'default-system' key. A more specific
  * match first to key and then to module will be used in preference to the
  * default. To specificy a different class for all mail sent by one module, set
@@ -195,11 +212,12 @@ function drupal_mail($module, $key, $to,
  *   A key to identify the e-mail sent. The final e-mail ID for the e-mail
  *   alter hook in drupal_mail() would have been {$module}_{$key}.
  */
-function drupal_mail_sending_system($module, $key) {
+function drupal_mail_system($module, $key) {
   $instances = &drupal_static(__FUNCTION__, array());
 
   $id = $module . '_' . $key;
-  $configuration = variable_get('mail_sending_system', array('default-system' => 'DefaultMailSystem'));
+
+  $configuration = variable_get('mail_system', array('default-system' => 'DefaultMailSystem'));
 
   // Look for overrides for the default class, starting from the most specific
   // id, and falling back to the module name.
@@ -230,7 +248,18 @@ function drupal_mail_sending_system($mod
  */
 interface MailSystemInterface {
   /**
-   * Send an e-mail message composed by drupal_mail().
+   * Format a message composed by drupal_mail() prior sending.
+   *
+   * @param $message
+   *   A message array, as described in hook_mail_alter().
+   *
+   * @return
+   *   The formatted $message.
+   */
+   public function format(array $message);
+
+  /**
+   * Send a message composed by drupal_mail().
    *
    * @param $message
    *   Message array with at least the following elements:
@@ -452,9 +481,10 @@ function drupal_html_to_text($string, $a
     }
     // Process blocks of text.
     else {
-      // Convert inline HTML text to plain text.
-      $value = trim(preg_replace('/\s+/', ' ', decode_entities($value)));
-      if (strlen($value)) {
+      // Convert inline HTML text to plain text; not removing line-breaks or
+      // white-space, since that breaks newlines when sanitizing plain-text.
+      $value = trim(decode_entities($value));
+      if (drupal_strlen($value)) {
         $chunk = $value;
       }
     }
@@ -466,7 +496,7 @@ function drupal_html_to_text($string, $a
         $chunk = $casing($chunk);
       }
       // Format it and apply the current indentation.
-      $output .= drupal_wrap_mail($chunk, implode('', $indent)) . "\n";
+      $output .= drupal_wrap_mail($chunk, implode('', $indent));
       // Remove non-quotation markers from indentation.
       $indent = array_map('_drupal_html_to_text_clean', $indent);
     }
Index: modules/contact/contact.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/contact/contact.test,v
retrieving revision 1.37
diff -u -p -r1.37 contact.test
--- modules/contact/contact.test	11 Oct 2009 18:34:10 -0000	1.37
+++ modules/contact/contact.test	15 Oct 2009 20:47:07 -0000
@@ -174,7 +174,7 @@ class ContactSitewideTestCase extends Dr
     // We are testing the auto-reply, so there should be one e-mail going to the sender.
     $captured_emails = $this->drupalGetMails(array('id' => 'contact_page_autoreply', 'to' => $email, 'from' => 'foo@example.com'));
     $this->assertEqual(count($captured_emails), 1, t('Auto-reply e-mail was sent to the sender for category "foo".'), t('Contact'));
-    $this->assertEqual($captured_emails[0]['body'], $foo_autoreply, t('Auto-reply e-mail body is correct for category "foo".'), t('Contact'));
+    $this->assertEqual($captured_emails[0]['body'], drupal_html_to_text($foo_autoreply), t('Auto-reply e-mail body is correct for category "foo".'), t('Contact'));
 
     // Test the auto-reply for category 'bar'.
     $email = $this->randomName(32) . '@example.com';
@@ -183,7 +183,7 @@ class ContactSitewideTestCase extends Dr
     // Auto-reply for category 'bar' should result in one auto-reply e-mail to the sender.
     $captured_emails = $this->drupalGetMails(array('id' => 'contact_page_autoreply', 'to' => $email, 'from' => 'bar@example.com'));
     $this->assertEqual(count($captured_emails), 1, t('Auto-reply e-mail was sent to the sender for category "bar".'), t('Contact'));
-    $this->assertEqual($captured_emails[0]['body'], $bar_autoreply, t('Auto-reply e-mail body is correct for category "bar".'), t('Contact'));
+    $this->assertEqual($captured_emails[0]['body'], drupal_html_to_text($bar_autoreply), t('Auto-reply e-mail body is correct for category "bar".'), t('Contact'));
 
     // Verify that no auto-reply is sent when the auto-reply field is left blank.
     $email = $this->randomName(32) . '@example.com';
Index: modules/simpletest/drupal_web_test_case.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v
retrieving revision 1.160
diff -u -p -r1.160 drupal_web_test_case.php
--- modules/simpletest/drupal_web_test_case.php	13 Oct 2009 07:14:26 -0000	1.160
+++ modules/simpletest/drupal_web_test_case.php	15 Oct 2009 04:05:07 -0000
@@ -1106,7 +1106,7 @@ class DrupalWebTestCase extends DrupalTe
     $language = language_default();
 
     // Use the test mail class instead of the default mail handler class.
-    variable_set('mail_sending_system', array('default-system' => 'TestingMailSystem'));
+    variable_set('mail_system', array('default-system' => 'TestingMailSystem'));
 
     // Use temporary files directory with the same prefix as the database.
     $public_files_directory  = $this->originalFileDirectory . '/' . $db_prefix;
Index: modules/simpletest/simpletest.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.test,v
retrieving revision 1.35
diff -u -p -r1.35 simpletest.test
--- modules/simpletest/simpletest.test	9 Oct 2009 07:48:06 -0000	1.35
+++ modules/simpletest/simpletest.test	15 Oct 2009 04:05:31 -0000
@@ -339,7 +339,7 @@ class SimpleTestMailCaptureTestCase exte
     $this->assertEqual(count($captured_emails), 0, t('The captured e-mails queue is empty.'), t('E-mail'));
 
     // Send the e-mail.
-    $response = drupal_mail_sending_system('simpletest', 'drupal_mail_test')->mail($message);
+    $response = drupal_mail_system('simpletest', 'drupal_mail_test')->mail($message);
 
     // Ensure that there is one e-mail in the captured e-mails array.
     $captured_emails = $this->drupalGetMails();
@@ -360,7 +360,7 @@ class SimpleTestMailCaptureTestCase exte
         'to' => $this->randomName(32) . '@example.com',
         'body' => $this->randomString(512),
       );
-      drupal_mail_sending_system('drupal_mail_test', $index)->mail($message);
+      drupal_mail_system('drupal_mail_test', $index)->mail($message);
     }
 
     // There should now be 6 e-mails captured.
@@ -377,7 +377,7 @@ class SimpleTestMailCaptureTestCase exte
 
     // Send the last e-mail again, so we can confirm that the drupalGetMails-filter
     // correctly returns all e-mails with a given property/value.
-    drupal_mail_sending_system('drupal_mail_test', $index)->mail($message);
+    drupal_mail_system('drupal_mail_test', $index)->mail($message);
     $captured_emails = $this->drupalGetMails(array('id' => 'drupal_mail_test_4'));
     $this->assertEqual(count($captured_emails), 2, t('All e-mails with the same id are returned when filtering by id.'), t('E-mail'));
   }
Index: modules/simpletest/tests/mail.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/mail.test,v
retrieving revision 1.1
diff -u -p -r1.1 mail.test
--- modules/simpletest/tests/mail.test	31 Aug 2009 18:30:26 -0000	1.1
+++ modules/simpletest/tests/mail.test	15 Oct 2009 04:05:54 -0000
@@ -25,7 +25,7 @@ class MailTestCase extends DrupalWebTest
     parent::setUp();
 
     // Set MailTestCase (i.e. this class) as the SMTP library
-    variable_set('mail_sending_system', array('default-system' => 'MailTestCase'));
+    variable_set('mail_system', array('default-system' => 'MailTestCase'));
   }
 
   /**
@@ -42,6 +42,21 @@ class MailTestCase extends DrupalWebTest
   }
 
   /**
+   * Concatenate and wrap the e-mail body for plain-text mails.
+   *
+   * @see DefaultMailSystem
+   */
+  public function format(array $message) {
+    // Join the body array into one string.
+    $message['body'] = implode("\n\n", $message['body']);
+    // Convert any HTML to plain-text.
+    $message['body'] = drupal_html_to_text($message['body']);
+    // Wrap the mail body for sending.
+    $message['body'] = drupal_wrap_mail($message['body']);
+    return $message;
+  }
+
+  /**
    * Send function that is called through the mail system.
    */
   public function mail(array $message) {
Index: modules/system/mail.sending.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/mail.sending.inc,v
retrieving revision 1.1
diff -u -p -r1.1 mail.sending.inc
--- modules/system/mail.sending.inc	31 Aug 2009 18:30:27 -0000	1.1
+++ modules/system/mail.sending.inc	15 Oct 2009 03:51:52 -0000
@@ -3,21 +3,40 @@
 
 /**
  * @file
- * Drupal core implementations of the DrupalMailSendingInterface.
+ * Drupal core implementations of MailSystemInterface.
  */
 
 /**
- * The default Drupal mail sending library using PHP's mail function.
+ * The default Drupal mail backend using PHP's mail function.
  */
 class DefaultMailSystem implements MailSystemInterface {
   /**
+   * Concatenate and wrap the e-mail body for plain-text mails.
+   *
+   * @param $message
+   *   A message array, as described in hook_mail_alter().
+   *
+   * @return
+   *   The formatted $message.
+   */
+  public function format(array $message) {
+    // Join the body array into one string.
+    $message['body'] = implode("\n\n", $message['body']);
+    // Convert any HTML to plain-text.
+    $message['body'] = drupal_html_to_text($message['body']);
+    // Wrap the mail body for sending.
+    $message['body'] = drupal_wrap_mail($message['body']);
+    return $message;
+  }
+
+  /**
    * Send an e-mail message, using Drupal variables and default settings.
-   * @see http://php.net/manual/en/function.mail.php the PHP function reference
-   *   for mail().
-   * @see drupal_mail() for information on how $message is composed.
+   *
+   * @see http://php.net/manual/en/function.mail.php
+   * @see drupal_mail()
    *
    * @param $message
-   *   Message array as described by DrupalMailSendingInterface.
+   *   A message array, as described in hook_mail_alter().
    * @return
    *   TRUE if the mail was successfully accepted, otherwise FALSE.
    */
@@ -44,8 +63,7 @@ class DefaultMailSystem implements MailS
  *
  * This class is for running tests or for development.
  */
-class TestingMailSystem implements MailSystemInterface {
-
+class TestingMailSystem extends DefaultMailSystem implements MailSystemInterface {
   /**
    * Accept an e-mail message and store it in a variable.
    *
Index: modules/system/system.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.api.php,v
retrieving revision 1.90
diff -u -p -r1.90 system.api.php
--- modules/system/system.api.php	15 Oct 2009 17:55:55 -0000	1.90
+++ modules/system/system.api.php	15 Oct 2009 20:25:42 -0000
@@ -700,7 +700,7 @@ function hook_image_toolkits() {
  *
  * Email messages sent using functions other than drupal_mail() will not
  * invoke hook_mail_alter(). For example, a contributed module directly
- * calling the drupal_mail_sending_system()->mail() or PHP mail() function
+ * calling the drupal_mail_system()->mail() or PHP mail() function
  * will not invoke this hook. All core modules use drupal_mail() for
  * messaging, it is best practice but not manditory in contributed modules.
  *
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.815
diff -u -p -r1.815 system.module
--- modules/system/system.module	15 Oct 2009 17:55:55 -0000	1.815
+++ modules/system/system.module	15 Oct 2009 20:25:43 -0000
@@ -2698,7 +2698,7 @@ function system_mail($key, &$message, $p
   $body = token_replace($context['message'], $context);
 
   $message['subject'] .= str_replace(array("\r", "\n"), '', $subject);
-  $message['body'][] = drupal_html_to_text($body);
+  $message['body'][] = $body;
 }
 
 function system_message_action_form($context) {
Index: modules/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.1064
diff -u -p -r1.1064 user.module
--- modules/user/user.module	15 Oct 2009 11:47:25 -0000	1.1064
+++ modules/user/user.module	15 Oct 2009 20:25:43 -0000
@@ -2910,8 +2910,7 @@ function user_preferred_language($accoun
  * @param $language
  *  Optional language to use for the notification, overriding account language.
  * @return
- *  The return value from drupal_mail_sending_system()->mail(), if ends up
- *  being called.
+ *  The return value from drupal_mail_system()->mail(), if ends up being called.
  */
 function _user_mail_notify($op, $account, $language = NULL) {
   // By default, we always notify except for canceled and blocked.
