diff --git a/core/core.services.yml b/core/core.services.yml
index afb710bb05e92c57636176dac45c2d35843efe1b..c3021b6b447fd84b8f701339117256417104eb17 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -587,9 +587,9 @@ services:
   flood:
     class: Drupal\Core\Flood\DatabaseBackend
     arguments: ['@database', '@request']
-  mail.factory:
-    class: Drupal\Core\Mail\MailFactory
-    arguments: ['@config.factory']
+  plugin.manager.mail:
+    class: Drupal\Core\Mail\MailManager
+    arguments: ['@container.namespaces', '@cache.cache', '@language_manager', '@module_handler', '@config.factory']
   plugin.manager.condition:
     class: Drupal\Core\Condition\ConditionManager
     parent: default_plugin_manager
diff --git a/core/includes/mail.inc b/core/includes/mail.inc
index 6ddff9789a44d2bfcf99f902c2994a3baa2e9014..0a0896d7065bc3569fab1dd43f9dd0012e025f2c 100644
--- a/core/includes/mail.inc
+++ b/core/includes/mail.inc
@@ -84,7 +84,7 @@
  *   called to complete the $message structure which will already contain common
  *   defaults.
  * @param string $key
- *   A key to identify the e-mail sent. The final e-mail id for e-mail altering
+ *   A key to identify the e-mail sent. The final message ID for e-mail altering
  *   will be {$module}_{$key}.
  * @param string $to
  *   The e-mail address or addresses where the message will be sent to. The
@@ -193,23 +193,23 @@ function drupal_mail($module, $key, $to, $langcode, $params = array(), $reply =
 }
 
 /**
- * Returns an object that implements Drupal\Core\Mail\MailInterface.
+ * Returns an instance of the mail plugin to use for a given message ID.
  *
  * @param string $module
  *   The module name which was used by drupal_mail() to invoke hook_mail().
  * @param string $key
- *   A key to identify the e-mail sent. The final e-mail ID for the e-mail
+ *   A key to identify the e-mail sent. The final message ID for the e-mail
  *   alter hook in drupal_mail() would have been {$module}_{$key}.
  *
  * @return \Drupal\Core\Mail\MailInterface
- *   An object that implements Drupal\Core\Mail\MailInterface.
+ *   A mail plugin instance.
  *
- * @throws \Exception
+ * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
  *
- * @see \Drupal\Core\Mail\MailFactory::get()
+ * @see \Drupal\Core\Mail\MailManager::getInstance()
  */
 function drupal_mail_system($module, $key) {
-  return \Drupal::service('mail.factory')->get($module, $key);
+  return \Drupal::service('plugin.manager.mail')->getInstance(array('module' => $module, 'key' => $key));
 }
 
 /**
diff --git a/core/lib/Drupal/Core/Annotation/Mail.php b/core/lib/Drupal/Core/Annotation/Mail.php
new file mode 100644
index 0000000000000000000000000000000000000000..abfddd37acb0ee6476711ecef1f55e1b1cee1ab5
--- /dev/null
+++ b/core/lib/Drupal/Core/Annotation/Mail.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Annotation\Mail.
+ */
+
+namespace Drupal\Core\Annotation;
+
+use Drupal\Component\Annotation\Plugin;
+
+/**
+ * Defines a Mail annotation object.
+ *
+ * @Annotation
+ */
+class Mail extends Plugin {
+
+  /**
+   * The plugin ID.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The human-readable name of the mail plugin.
+   *
+   * @var \Drupal\Core\Annotation\Translation
+   *
+   * @ingroup plugin_translatable
+   */
+  public $label;
+
+  /**
+   * A short description of the mail plugin.
+   *
+   * @var \Drupal\Core\Annotation\Translation
+   *
+   * @ingroup plugin_translatable
+   */
+  public $description;
+
+}
diff --git a/core/lib/Drupal/Core/Mail/MailFactory.php b/core/lib/Drupal/Core/Mail/MailFactory.php
deleted file mode 100644
index 439fd64746dd48efb0c8cde2dbf093e028508dea..0000000000000000000000000000000000000000
--- a/core/lib/Drupal/Core/Mail/MailFactory.php
+++ /dev/null
@@ -1,126 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Core\Mail\MailFactory.
- */
-
-namespace Drupal\Core\Mail;
-
-use Drupal\Core\Config\ConfigFactoryInterface;
-use Drupal\Component\Utility\String;
-
-/**
- * Factory for creating mail system objects.
- */
-class MailFactory {
-
-  /**
-   * Config object for mail system configurations.
-   *
-   * @var \Drupal\Core\Config\Config
-   */
-  protected $mailConfig;
-
-  /**
-   * List of already instantiated mail system objects.
-   *
-   * @var array
-   */
-  protected $instances = array();
-
-  /**
-   * Constructs a MailFActory object.
-   *
-   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
-   *   The configuration factory.
-   */
-  public function __construct(ConfigFactoryInterface $configFactory) {
-    $this->mailConfig = $configFactory->get('system.mail');
-  }
-
-
-  /**
-   * Returns an object that implements \Drupal\Core\Mail\MailInterface.
-   *
-   * Allows for one or more custom mail backends to format and send mail messages
-   * composed using drupal_mail().
-   *
-   * The selection of a particular implementation is controlled via the config
-   * 'system.mail.interface', which is a keyed array.  The default
-   * implementation is the class whose name is the value of 'default' key. A
-   * more specific match first to key and then to module will be used in
-   * preference to the default. To specify a different class for all mail sent
-   * by one module, set the class name as the value for the key corresponding to
-   * the module name. To specify a class for a particular message sent by one
-   * module, set the class name as the value for the array key that is the
-   * message id, which is "${module}_${key}".
-   *
-   * For example to debug all mail sent by the user module by logging it to a
-   * file, you might set the variable as something like:
-   *
-   * @code
-   * array(
-   *   'default' => 'Drupal\Core\Mail\PhpMail',
-   *   'user' => 'Drupal\devel\DevelMailLog',
-   * );
-   * @endcode
-   *
-   * Finally, a different system can be specified for a specific e-mail ID (see
-   * the $key param), such as one of the keys used by the contact module:
-   *
-   * @code
-   * array(
-   *   'default' => 'Drupal\Core\Mail\PhpMail',
-   *   'user' => 'Drupal\devel\DevelMailLog',
-   *   'contact_page_autoreply' => 'Drupal\example\NullMail',
-   * );
-   * @endcode
-   *
-   * Other possible uses for system include a mail-sending class that actually
-   * sends (or duplicates) each message to SMS, Twitter, instant message, etc,
-   * or a class that queues up a large number of messages for more efficient
-   * bulk sending or for sending via a remote gateway so as to reduce the load
-   * on the local server.
-   *
-   * @param string $module
-   *   The module name which was used by drupal_mail() to invoke hook_mail().
-   * @param string $key
-   *   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}.
-   *
-   * @return \Drupal\Core\Mail\MailInterface
-   *   An object that implements Drupal\Core\Mail\MailInterface.
-   *
-   * @throws \Exception
-   */
-  public function get($module, $key) {
-    $id = $module . '_' . $key;
-
-    $configuration = $this->mailConfig->get('interface');
-
-    // Look for overrides for the default class, starting from the most specific
-    // id, and falling back to the module name.
-    if (isset($configuration[$id])) {
-      $class = $configuration[$id];
-    }
-    elseif (isset($configuration[$module])) {
-      $class = $configuration[$module];
-    }
-    else {
-      $class = $configuration['default'];
-    }
-
-    if (empty($this->instances[$class])) {
-      $interfaces = class_implements($class);
-      if (isset($interfaces['Drupal\Core\Mail\MailInterface'])) {
-        $this->instances[$class] = new $class();
-      }
-      else {
-        throw new \Exception(String::format('Class %class does not implement interface %interface', array('%class' => $class, '%interface' => 'Drupal\Core\Mail\MailInterface')));
-      }
-    }
-    return $this->instances[$class];
-  }
-
-}
diff --git a/core/lib/Drupal/Core/Mail/MailInterface.php b/core/lib/Drupal/Core/Mail/MailInterface.php
index d3e317351d64fcee1dea4d64aa7bfa898ce7cb90..62a63197ff32a360b4cf4996b9d998170dfdd18a 100644
--- a/core/lib/Drupal/Core/Mail/MailInterface.php
+++ b/core/lib/Drupal/Core/Mail/MailInterface.php
@@ -17,10 +17,10 @@ interface MailInterface {
    *
    * 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 Drupal\Core\Mail\PhpMail 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 Drupal\Core\Mail\PhpMail
+   * are converted to plain-text by the Drupal\Core\Mail\Plugin\Mail\PhpMail
+   * implementation. For example, an alternative implementation could override
+   * the default implementation and also sanitize the HTML for usage in a MIME-
+   * encoded email, but still invoking the Drupal\Core\Mail\Plugin\Mail\PhpMail
    * implementation to generate an alternate plain-text version for sending.
    *
    * @param array $message
diff --git a/core/lib/Drupal/Core/Mail/MailManager.php b/core/lib/Drupal/Core/Mail/MailManager.php
new file mode 100644
index 0000000000000000000000000000000000000000..9f012813591b7f7e8647bfb06df128a7da0875b3
--- /dev/null
+++ b/core/lib/Drupal/Core/Mail/MailManager.php
@@ -0,0 +1,143 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Mail\MailManager.
+ */
+
+namespace Drupal\Core\Mail;
+
+use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Component\Utility\String;
+use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
+
+/**
+ * Mail plugin manager.
+ */
+class MailManager extends DefaultPluginManager {
+
+  /**
+   * Config object for mail system configurations.
+   *
+   * @var \Drupal\Core\Config\Config
+   */
+  protected $mailConfig;
+
+  /**
+   * List of already instantiated mail plugins.
+   *
+   * @var array
+   */
+  protected $instances = array();
+
+  /**
+   * Constructs the MailManager object.
+   *
+   * @param \Traversable $namespaces
+   *   An object that implements \Traversable which contains the root paths
+   *   keyed by the corresponding namespace to look for plugin implementations.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   Cache backend instance to use.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+   *   The language manager.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler to invoke the alter hook with.
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The configuration factory.
+   */
+  public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, LanguageManagerInterface $language_manager, ModuleHandlerInterface $module_handler, ConfigFactoryInterface $config_factory) {
+    parent::__construct('Plugin/Mail', $namespaces, 'Drupal\Core\Annotation\Mail');
+    $this->alterInfo($module_handler, 'mail_backend_info');
+    $this->setCacheBackend($cache_backend, $language_manager, 'mail_backend_plugins');
+    $this->mailConfig = $config_factory->get('system.mail');
+  }
+
+  /**
+   * Overrides PluginManagerBase::getInstance().
+   *
+   * Returns an instance of the mail plugin to use for a given message ID.
+   *
+   * The selection of a particular implementation is controlled via the config
+   * 'system.mail.interface', which is a keyed array.  The default
+   * implementation is the mail plugin whose ID is the value of 'default' key. A
+   * more specific match first to key and then to module will be used in
+   * preference to the default. To specify a different plugin for all mail sent
+   * by one module, set the plugin ID as the value for the key corresponding to
+   * the module name. To specify a plugin for a particular message sent by one
+   * module, set the plugin ID as the value for the array key that is the
+   * message ID, which is "${module}_${key}".
+   *
+   * For example to debug all mail sent by the user module by logging it to a
+   * file, you might set the variable as something like:
+   *
+   * @code
+   * array(
+   *   'default' => 'php_mail',
+   *   'user' => 'devel_mail_log',
+   * );
+   * @endcode
+   *
+   * Finally, a different system can be specified for a specific message ID (see
+   * the $key param), such as one of the keys used by the contact module:
+   *
+   * @code
+   * array(
+   *   'default' => 'php_mail',
+   *   'user' => 'devel_mail_log',
+   *   'contact_page_autoreply' => 'null_mail',
+   * );
+   * @endcode
+   *
+   * Other possible uses for system include a mail-sending plugin that actually
+   * sends (or duplicates) each message to SMS, Twitter, instant message, etc,
+   * or a plugin that queues up a large number of messages for more efficient
+   * bulk sending or for sending via a remote gateway so as to reduce the load
+   * on the local server.
+   *
+   * @param array $options
+   *   An array with the following key/value pairs:
+   *   - module: (string) The module name which was used by drupal_mail() to
+   *     invoke hook_mail().
+   *   - key: (string) A key to identify the email sent. The final message ID
+   *     is a string represented as {$module}_{$key}.
+   *
+   * @return \Drupal\Core\Mail\MailInterface
+   *   A mail plugin instance.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+   */
+  public function getInstance(array $options) {
+    $module = $options['module'];
+    $key = $options['key'];
+    $message_id = $module . '_' . $key;
+
+    $configuration = $this->mailConfig->get('interface');
+
+    // Look for overrides for the default mail plugin, starting from the most
+    // specific message_id, and falling back to the module name.
+    if (isset($configuration[$message_id])) {
+      $plugin_id = $configuration[$message_id];
+    }
+    elseif (isset($configuration[$module])) {
+      $plugin_id = $configuration[$module];
+    }
+    else {
+      $plugin_id = $configuration['default'];
+    }
+
+    if (empty($this->instances[$plugin_id])) {
+      $plugin = $this->createInstance($plugin_id);
+      if (is_subclass_of($plugin, '\Drupal\Core\Mail\MailInterface')) {
+        $this->instances[$plugin_id] = $plugin;
+      }
+      else {
+        throw new InvalidPluginDefinitionException($plugin_id, String::format('Class %class does not implement interface %interface', array('%class' => get_class($plugin), '%interface' => 'Drupal\Core\Mail\MailInterface')));
+      }
+    }
+    return $this->instances[$plugin_id];
+  }
+}
diff --git a/core/lib/Drupal/Core/Mail/PhpMail.php b/core/lib/Drupal/Core/Mail/Plugin/Mail/PhpMail.php
similarity index 90%
rename from core/lib/Drupal/Core/Mail/PhpMail.php
rename to core/lib/Drupal/Core/Mail/Plugin/Mail/PhpMail.php
index 7bf290c10775ebb9463d202e639198921c5c2b58..2d1f3281577e1db61e64ced6fc27278d22c0f713 100644
--- a/core/lib/Drupal/Core/Mail/PhpMail.php
+++ b/core/lib/Drupal/Core/Mail/Plugin/Mail/PhpMail.php
@@ -2,13 +2,21 @@
 
 /**
  * @file
- * Definition of Drupal\Core\Mail\PhpMail.
+ * Contains Drupal\Core\Mail\Plugin\Mail\PhpMail.
  */
 
-namespace Drupal\Core\Mail;
+namespace Drupal\Core\Mail\Plugin\Mail;
+
+use Drupal\Core\Mail\MailInterface;
 
 /**
- * The default Drupal mail backend using PHP's mail function.
+ * Defines the default Drupal mail backend, using PHP's native mail() function.
+ *
+ * @Mail(
+ *   id = "php_mail",
+ *   label = @Translation("Default PHP mailer"),
+ *   description = @Translation("Sends the message as plain text, using PHP's native mail() function.")
+ * )
  */
 class PhpMail implements MailInterface {
 
diff --git a/core/lib/Drupal/Core/Mail/Plugin/Mail/TestMailCollector.php b/core/lib/Drupal/Core/Mail/Plugin/Mail/TestMailCollector.php
new file mode 100644
index 0000000000000000000000000000000000000000..1579e885e52c7d77cd22f06d79983322c0a93fe8
--- /dev/null
+++ b/core/lib/Drupal/Core/Mail/Plugin/Mail/TestMailCollector.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Mail\Plugin\Mail\TestMailCollector.
+ */
+
+namespace Drupal\Core\Mail\Plugin\Mail;
+
+use Drupal\Core\Mail\MailInterface;
+
+/**
+ * Defines a mail backend that captures sent messages in the state system.
+ *
+ * This class is for running tests or for development.
+ *
+ * @Mail(
+ *   id = "test_mail_collector",
+ *   label = @Translation("Mail collector"),
+ *   description = @Translation("Does not send the message, but stores it in Drupal within the state system. Used for testing.")
+ * )
+ */
+class TestMailCollector extends PhpMail implements MailInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function mail(array $message) {
+    $captured_emails = \Drupal::state()->get('system.test_mail_collector') ?: array();
+    $captured_emails[] = $message;
+    \Drupal::state()->set('system.test_mail_collector', $captured_emails);
+
+    return TRUE;
+  }
+}
diff --git a/core/lib/Drupal/Core/Mail/TestMailCollector.php b/core/lib/Drupal/Core/Mail/TestMailCollector.php
deleted file mode 100644
index 3b3e0566e64dd03e301238900a60221aa0687b7e..0000000000000000000000000000000000000000
--- a/core/lib/Drupal/Core/Mail/TestMailCollector.php
+++ /dev/null
@@ -1,30 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Core\Mail\TestMailCollector.
- */
-
-namespace Drupal\Core\Mail;
-
-/**
- * Defines a mail sending implementation that captures sent messages to the
- * state system.
- *
- * This class is for running tests or for development.
- */
-class TestMailCollector extends PhpMail implements MailInterface {
-
-  /**
-   * Overrides \Drupal\Core\Mail\PhpMail::mail().
-   *
-   * Accepts an e-mail message and stores it with the state system.
-   */
-  public function mail(array $message) {
-    $captured_emails = \Drupal::state()->get('system.test_mail_collector') ?: array();
-    $captured_emails[] = $message;
-    \Drupal::state()->set('system.test_mail_collector', $captured_emails);
-
-    return TRUE;
-  }
-}
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/InstallerTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/InstallerTestBase.php
index 3f8d0c4487793df86319b32b760259e3c24c04ba..1da7f9f58a17bf01f2c2e069880d75f731210acd 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/InstallerTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/InstallerTestBase.php
@@ -131,7 +131,7 @@ protected function setUp() {
     // Manually configure the test mail collector implementation to prevent
     // tests from sending out e-mails and collect them in state instead.
     \Drupal::config('system.mail')
-      ->set('interface.default', 'Drupal\Core\Mail\TestMailCollector')
+      ->set('interface.default', 'test_mail_collector')
       ->save();
 
     // When running from run-tests.sh we don't get an empty current path which
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
index 97954558159d2cb55b9faa0a03d579c8dc423df0..9928b68d686318463c110dbe20e2615bb2e18801 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
@@ -840,7 +840,7 @@ protected function setUp() {
     // While this should be enforced via settings.php prior to installation,
     // some tests expect to be able to test mail system implementations.
     \Drupal::config('system.mail')
-      ->set('interface.default', 'Drupal\Core\Mail\TestMailCollector')
+      ->set('interface.default', 'test_mail_collector')
       ->save();
 
     // Restore the original Simpletest batch.
diff --git a/core/modules/system/config/system.mail.yml b/core/modules/system/config/system.mail.yml
index e3eb7685187a5da626b48739485d144e84e45c8c..a7147a7bd947dfed81989e80fed660d62dfc1480 100644
--- a/core/modules/system/config/system.mail.yml
+++ b/core/modules/system/config/system.mail.yml
@@ -1,2 +1,2 @@
 interface:
- default: 'Drupal\Core\Mail\PhpMail'
+ default: 'php_mail'
diff --git a/core/modules/system/lib/Drupal/system/Tests/Mail/MailTest.php b/core/modules/system/lib/Drupal/system/Tests/Mail/MailTest.php
index 025c190db00c575a76644d7b185900ce87c59535..56aef7f98ae008caec2015c7ab20855c5a2c732c 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Mail/MailTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Mail/MailTest.php
@@ -8,20 +8,19 @@
 namespace Drupal\system\Tests\Mail;
 
 use Drupal\Core\Language\Language;
-use Drupal\Core\Mail\MailInterface;
 use Drupal\simpletest\WebTestBase;
 
 /**
- * Defines a mail class used for testing.
+ * Tests related to the mail system.
  */
-class MailTest extends WebTestBase implements MailInterface {
+class MailTest extends WebTestBase {
 
   /**
    * Modules to enable.
    *
    * @var array
    */
-  public static $modules = array('simpletest');
+  public static $modules = array('simpletest', 'system_mail_failure_test');
 
   /**
    * The most recent message that was sent through the test case.
@@ -39,24 +38,19 @@ public static function getInfo() {
     );
   }
 
-  function setUp() {
-    parent::setUp();
-
-    // Set MailTestCase (i.e. this class) as the SMTP library
-    \Drupal::config('system.mail')->set('interface.default', 'Drupal\system\Tests\Mail\MailTest')->save();
-  }
-
   /**
    * Assert that the pluggable mail system is functional.
    */
   public function testPluggableFramework() {
-    $language_interface = \Drupal::languageManager()->getCurrentLanguage();
+    // Switch mail backends.
+    \Drupal::config('system.mail')->set('interface.default', 'test_php_mail_failure')->save();
 
-    // Use MailTestCase for sending a message.
-    drupal_mail('simpletest', 'mail_test', 'testing@example.com', $language_interface->id);
+    // Get the default MailInterface class instance.
+    $mail_backend = drupal_mail_system('default', 'default');
 
-    // Assert whether the message was sent through the send function.
-    $this->assertEqual(self::$sent_message['to'], 'testing@example.com', 'Pluggable mail system is extendable.');
+    // Assert whether the default mail backend is an instance of the expected
+    // class.
+    $this->assertTrue($mail_backend instanceof \Drupal\system_mail_failure_test\Plugin\Mail\TestPhpMailFailure, 'Pluggable mail system is extendable.');
   }
 
   /**
@@ -67,14 +61,19 @@ public function testPluggableFramework() {
   public function testCancelMessage() {
     $language_interface = \Drupal::languageManager()->getCurrentLanguage();
 
-    // Reset the class variable holding a copy of the last sent message.
-    self::$sent_message = NULL;
+    // Use the state system collector mail backend.
+    \Drupal::config('system.mail')->set('interface.default', 'test_mail_collector')->save();
+    // Reset the state variable that holds sent messages.
+    \Drupal::state()->set('system.test_mail_collector', array());
 
     // Send a test message that simpletest_mail_alter should cancel.
     drupal_mail('simpletest', 'cancel_test', 'cancel@example.com', $language_interface->id);
+    // Retrieve sent message.
+    $captured_emails = \Drupal::state()->get('system.test_mail_collector');
+    $sent_message = end($captured_emails);
 
     // Assert that the message was not actually sent.
-    $this->assertNull(self::$sent_message, 'Message was canceled.');
+    $this->assertFalse($sent_message, 'Message was canceled.');
   }
 
   /**
@@ -83,45 +82,28 @@ public function testCancelMessage() {
   public function testFromAndReplyToHeader() {
     $language = \Drupal::languageManager()->getCurrentLanguage();
 
-    // Reset the class variable holding a copy of the last sent message.
-    self::$sent_message = NULL;
+    // Use the state system collector mail backend.
+    \Drupal::config('system.mail')->set('interface.default', 'test_mail_collector')->save();
+    // Reset the state variable that holds sent messages.
+    \Drupal::state()->set('system.test_mail_collector', array());
     // Send an e-mail with a reply-to address specified.
     $from_email = 'Drupal <simpletest@example.com>';
     $reply_email = 'someone_else@example.com';
     drupal_mail('simpletest', 'from_test', 'from_test@example.com', $language, array(), $reply_email);
-    // Test that the reply-to e-mail is just the e-mail and not the site name and
-    // default sender e-mail.
-    $this->assertEqual($from_email, self::$sent_message['headers']['From'], 'Message is sent from the site email account.');
-    $this->assertEqual($reply_email, self::$sent_message['headers']['Reply-to'], 'Message reply-to headers are set.');
-    $this->assertFalse(isset(self::$sent_message['headers']['Errors-To']), 'Errors-to header must not be set, it is deprecated.');
+    // Test that the reply-to e-mail is just the e-mail and not the site name
+    // and default sender e-mail.
+    $captured_emails = \Drupal::state()->get('system.test_mail_collector');
+    $sent_message = end($captured_emails);
+    $this->assertEqual($from_email, $sent_message['headers']['From'], 'Message is sent from the site email account.');
+    $this->assertEqual($reply_email, $sent_message['headers']['Reply-to'], 'Message reply-to headers are set.');
+    $this->assertFalse(isset($sent_message['headers']['Errors-To']), 'Errors-to header must not be set, it is deprecated.');
 
-    self::$sent_message = NULL;
     // Send an e-mail and check that the From-header contains the site name.
     drupal_mail('simpletest', 'from_test', 'from_test@example.com', $language);
-    $this->assertEqual($from_email, self::$sent_message['headers']['From'], 'Message is sent from the site email account.');
-    $this->assertFalse(isset(self::$sent_message['headers']['Reply-to']), 'Message reply-to is not set if not specified.');
-    $this->assertFalse(isset(self::$sent_message['headers']['Errors-To']), 'Errors-to header must not be set, it is deprecated.');
-  }
-
-  /**
-   * Concatenate and wrap the e-mail body for plain-text mails.
-   *
-   * @see \Drupal\Core\Mail\PhpMail
-   */
-  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) {
-    self::$sent_message = $message;
+    $captured_emails = \Drupal::state()->get('system.test_mail_collector');
+    $sent_message = end($captured_emails);
+    $this->assertEqual($from_email, $sent_message['headers']['From'], 'Message is sent from the site email account.');
+    $this->assertFalse(isset($sent_message['headers']['Reply-to']), 'Message reply-to is not set if not specified.');
+    $this->assertFalse(isset($sent_message['headers']['Errors-To']), 'Errors-to header must not be set, it is deprecated.');
   }
 }
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
index 19ea5b1b75e2772116296b021305f5fe76bcded1..843629e3201185e0d6822a9356038389e18343c0 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -2559,6 +2559,19 @@ function hook_archiver_info_alter(&$info) {
   $info['tar']['extensions'][] = 'tgz';
 }
 
+/**
+ * Alter the list of mail backend plugin definitions.
+ *
+ * @param array $info
+ *   The mail backend plugin definitions to be altered.
+ *
+ * @see \Drupal\Core\Annotation\Mail
+ * @see \Drupal\Core\Mail\MailManager
+ */
+function hook_mail_backend_info_alter(&$info) {
+  unset($info['test_mail_collector']);
+}
+
 /**
  * Alters theme operation links.
  *
diff --git a/core/modules/system/tests/modules/system_mail_failure_test/lib/Drupal/system_mail_failure_test/Plugin/Mail/TestPhpMailFailure.php b/core/modules/system/tests/modules/system_mail_failure_test/lib/Drupal/system_mail_failure_test/Plugin/Mail/TestPhpMailFailure.php
new file mode 100644
index 0000000000000000000000000000000000000000..a4c6a3461c390bb16a6ef957a96756c38bfd5c36
--- /dev/null
+++ b/core/modules/system/tests/modules/system_mail_failure_test/lib/Drupal/system_mail_failure_test/Plugin/Mail/TestPhpMailFailure.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system_mail_failure_test\Plugin\Mail\TestPhpMailFailure.
+ */
+
+namespace Drupal\system_mail_failure_test\Plugin\Mail;
+
+use Drupal\Core\Mail\Plugin\Mail\PhpMail;
+use Drupal\Core\Mail\MailInterface;
+
+/**
+ * Defines a mail sending implementation that always fails.
+ *
+ * This class is for running tests or for development. To use set the
+ * configuration:
+ * @code
+ *   \Drupal::config('system.mail')->set('interface.default', 'test_php_mail_failure')->save();
+ * @endcode
+ *
+ * @Mail(
+ *   id = "test_php_mail_failure",
+ *   label = @Translation("Malfunctioning mail backend"),
+ *   description = @Translation("An intentionally broken mail backend, used for tests.")
+ * )
+ */
+class TestPhpMailFailure extends PhpMail implements MailInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function mail(array $message) {
+    // Simulate a failed mail send by returning FALSE.
+    return FALSE;
+  }
+}
diff --git a/core/modules/system/tests/modules/system_mail_failure_test/lib/Drupal/system_mail_failure_test/TestPhpMailFailure.php b/core/modules/system/tests/modules/system_mail_failure_test/lib/Drupal/system_mail_failure_test/TestPhpMailFailure.php
deleted file mode 100644
index f20472a60a7753d20c7c2643a12a41cc09533540..0000000000000000000000000000000000000000
--- a/core/modules/system/tests/modules/system_mail_failure_test/lib/Drupal/system_mail_failure_test/TestPhpMailFailure.php
+++ /dev/null
@@ -1,31 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\system_mail_failure_test\TestPhpMailFailure.
- */
-
-namespace Drupal\system_mail_failure_test;
-
-use Drupal\Core\Mail\PhpMail;
-use Drupal\Core\Mail\MailInterface;
-
-/**
- * Defines a mail sending implementation that returns false.
- *
- * This class is for running tests or for development. To use set the
- * configuration:
- * @code
- *   \Drupal::config('system.mail')->set('interface.default', 'Drupal\system_mail_failure_test\TestPhpMailFailure')->save();
- * @endcode
- */
-class TestPhpMailFailure extends PhpMail implements MailInterface {
-
-  /**
-   * Overrides Drupal\Core\Mail\PhpMail::mail().
-   */
-  public function mail(array $message) {
-    // Instead of attempting to send a message, just return failure.
-    return FALSE;
-  }
-}
diff --git a/core/modules/user/lib/Drupal/user/Tests/UserCreateFailMailTest.php b/core/modules/user/lib/Drupal/user/Tests/UserCreateFailMailTest.php
index 6909667af8476aa37b0295bdc39dbec320e0f2c0..5a7dabe8fb82cd91aed88892b5420dea3b2ddbde 100644
--- a/core/modules/user/lib/Drupal/user/Tests/UserCreateFailMailTest.php
+++ b/core/modules/user/lib/Drupal/user/Tests/UserCreateFailMailTest.php
@@ -37,7 +37,7 @@ protected function testUserAdd() {
     $this->drupalLogin($user);
 
     // Replace the mail functionality with a fake, malfunctioning service.
-    \Drupal::config('system.mail')->set('interface.default', 'Drupal\system_mail_failure_test\TestPhpMailFailure')->save();
+    \Drupal::config('system.mail')->set('interface.default', 'test_php_mail_failure')->save();
     // Create a user, but fail to send an email.
     $name = $this->randomName();
     $edit = array(
diff --git a/core/tests/Drupal/Tests/Core/Mail/MailManagerTest.php b/core/tests/Drupal/Tests/Core/Mail/MailManagerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..9ef8d20ab2d31c3e6135295778e93ee4afb04bfa
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Mail/MailManagerTest.php
@@ -0,0 +1,157 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Mail\MailManagerTest.
+ */
+
+namespace Drupal\Tests\Core\Mail;
+
+use Drupal\Tests\UnitTestCase;
+use Drupal\Core\Mail\MailManager;
+use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
+
+/**
+ * Tests the mail plugin manager.
+ *
+ * @group Drupal
+ * @group Mail
+ *
+ * @see \Drupal\Core\Mail\MailManager
+ */
+class MailManagerTest extends UnitTestCase {
+
+  /**
+   * The cache backend to use.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $cache;
+
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $languageManager;
+
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $moduleHandler;
+
+  /**
+   * The configuration factory.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $configFactory;
+
+  /**
+   * The plugin discovery.
+   *
+   * @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $discovery;
+
+  /**
+   * A list of mail plugin definitions.
+   *
+   * @var array
+   */
+  protected $definitions = array(
+    'php_mail' => array(
+      'id' => 'php_mail',
+      'class' => 'Drupal\Core\Mail\Plugin\Mail\PhpMail',
+    ),
+    'test_mail_collector' => array(
+      'id' => 'test_mail_collector',
+      'class' => 'Drupal\Core\Mail\Plugin\Mail\TestMailCollector',
+    ),
+  );
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Mail manager test',
+      'description' => 'Tests the mail plugin manager.',
+      'group' => 'Mail',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    // Prepare the default constructor arguments required by MailManager.
+    $this->cache = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
+
+    $this->languageManager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
+    $this->languageManager->expects($this->any())
+      ->method('getCurrentLanguage')
+      ->will($this->returnValue((object) array('id' => 'en')));
+
+    $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
+
+    // Mock a Discovery object to replace AnnotationClassDiscovery.
+    $this->discovery = $this->getMock('Drupal\Component\Plugin\Discovery\DiscoveryInterface');
+    $this->discovery->expects($this->any())
+      ->method('getDefinitions')
+      ->will($this->returnValue($this->definitions));
+  }
+
+  /**
+   * Sets up the mail manager for testing.
+   */
+  protected function setUpMailManager($interface = array()) {
+    // Use the provided config for system.mail.interface settings.
+    $this->configFactory = $this->getConfigFactoryStub(array('system.mail' => array(
+      'interface' => $interface,
+    )));
+    // Construct the manager object and override its discovery.
+    $this->mailManager = new TestMailManager(new \ArrayObject(), $this->cache, $this->languageManager, $this->moduleHandler, $this->configFactory);
+    $this->mailManager->setDiscovery($this->discovery);
+  }
+
+  /**
+   * Tests the getInstance method.
+   *
+   * @covers \Drupal\Core\Mail\MailManager::getInstance()
+   */
+  public function testGetInstance() {
+    $interface = array(
+      'default' => 'php_mail',
+      'example_testkey' => 'test_mail_collector',
+    );
+    $this->setUpMailManager($interface);
+
+    // Test that an unmatched message_id returns the default plugin instance.
+    $options = array('module' => 'foo', 'key' => 'bar');
+    $instance = $this->mailManager->getInstance($options);
+    $this->assertInstanceOf('Drupal\Core\Mail\Plugin\Mail\PhpMail', $instance);
+
+    // Test that a matching message_id returns the specified plugin instance.
+    $options = array('module' => 'example', 'key' => 'testkey');
+    $instance = $this->mailManager->getInstance($options);
+    $this->assertInstanceOf('Drupal\Core\Mail\Plugin\Mail\TestMailCollector', $instance);
+  }
+}
+
+/**
+ * Provides a testing version of MailManager with an empty constructor.
+ */
+class TestMailManager extends MailManager {
+  /**
+   * Sets the discovery for the manager.
+   *
+   * @param \Drupal\Component\Plugin\Discovery\DiscoveryInterface $discovery
+   *   The discovery object.
+   */
+  public function setDiscovery(DiscoveryInterface $discovery) {
+    $this->discovery = $discovery;
+  }
+}