diff --git a/src/applications/metamta/adapter/PhabricatorMailImplementationAdapter.php b/src/applications/metamta/adapter/PhabricatorMailImplementationAdapter.php
--- a/src/applications/metamta/adapter/PhabricatorMailImplementationAdapter.php
+++ b/src/applications/metamta/adapter/PhabricatorMailImplementationAdapter.php
@@ -3,6 +3,7 @@
 abstract class PhabricatorMailImplementationAdapter extends Phobject {
 
   private $key;
+  private $priority;
   private $options = array();
 
   final public function getAdapterType() {
@@ -57,6 +58,15 @@
     return $this->key;
   }
 
+  final public function setPriority($priority) {
+    $this->priority = $priority;
+    return $this;
+  }
+
+  final public function getPriority() {
+    return $this->priority;
+  }
+
   final public function getOption($key) {
     if (!array_key_exists($key, $this->options)) {
       throw new Exception(
diff --git a/src/applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php b/src/applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php
--- a/src/applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php
+++ b/src/applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php
@@ -8,14 +8,31 @@
   }
 
   private function verifyMessage() {
-    $api_key = PhabricatorEnv::getEnvConfig('mailgun.api-key');
     $request = $this->getRequest();
     $timestamp = $request->getStr('timestamp');
     $token = $request->getStr('token');
     $sig = $request->getStr('signature');
-    $hash = hash_hmac('sha256', $timestamp.$token, $api_key);
 
-    return phutil_hashes_are_identical($sig, $hash);
+    // An install may configure multiple Mailgun mailers, and we might receive
+    // inbound mail from any of them. Test the signature to see if it matches
+    // any configured Mailgun mailer.
+
+    $mailers = PhabricatorMetaMTAMail::newMailers();
+    $mailgun_type = PhabricatorMailImplementationMailgunAdapter::ADAPTERTYPE;
+    foreach ($mailers as $mailer) {
+      if ($mailer->getAdapterType() != $mailgun_type) {
+        continue;
+      }
+
+      $api_key = $mailer->getOption('api-key');
+
+      $hash = hash_hmac('sha256', $timestamp.$token, $api_key);
+      if (phutil_hashes_are_identical($sig, $hash)) {
+        return true;
+      }
+    }
+
+    return false;
   }
 
   public function handleRequest(AphrontRequest $request) {
diff --git a/src/applications/metamta/controller/PhabricatorMetaMTASendGridReceiveController.php b/src/applications/metamta/controller/PhabricatorMetaMTASendGridReceiveController.php
--- a/src/applications/metamta/controller/PhabricatorMetaMTASendGridReceiveController.php
+++ b/src/applications/metamta/controller/PhabricatorMetaMTASendGridReceiveController.php
@@ -8,6 +8,26 @@
   }
 
   public function handleRequest(AphrontRequest $request) {
+    $mailers = PhabricatorMetaMTAMail::newMailers();
+    $sendgrid_type = PhabricatorMailImplementationSendGridAdapter::ADAPTERTYPE;
+
+    // SendGrid doesn't sign payloads so we can't be sure that SendGrid
+    // actually sent this request, but require a configured SendGrid mailer
+    // before we activate this endpoint.
+
+    $has_sendgrid = false;
+    foreach ($mailers as $mailer) {
+      if ($mailer->getAdapterType() != $sendgrid_type) {
+        continue;
+      }
+
+      $has_sendgrid = true;
+      break;
+    }
+
+    if (!$has_sendgrid) {
+      return new Aphront404Response();
+    }
 
     // No CSRF for SendGrid.
     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
diff --git a/src/applications/metamta/storage/PhabricatorMetaMTAMail.php b/src/applications/metamta/storage/PhabricatorMetaMTAMail.php
--- a/src/applications/metamta/storage/PhabricatorMetaMTAMail.php
+++ b/src/applications/metamta/storage/PhabricatorMetaMTAMail.php
@@ -463,33 +463,85 @@
       throw new Exception(pht('Trying to send an already-sent mail!'));
     }
 
-    $mailers = $this->newMailers();
+    $mailers = self::newMailers();
 
     return $this->sendWithMailers($mailers);
   }
 
-  private function newMailers() {
+  public static function newMailers() {
     $mailers = array();
 
-    $mailer = PhabricatorEnv::newObjectFromConfig('metamta.mail-adapter');
+    $config = PhabricatorEnv::getEnvConfig('cluster.mailers');
+    if ($config === null) {
+      $mailer = PhabricatorEnv::newObjectFromConfig('metamta.mail-adapter');
 
-    $defaults = $mailer->newDefaultOptions();
-    $options = $mailer->newLegacyOptions();
+      $defaults = $mailer->newDefaultOptions();
+      $options = $mailer->newLegacyOptions();
 
-    $options = $options + $defaults;
+      $options = $options + $defaults;
 
-    $mailer
-      ->setKey('default')
-      ->setOptions($options);
+      $mailer
+        ->setKey('default')
+        ->setPriority(-1)
+        ->setOptions($options);
 
-    $mailer->prepareForSend();
+      $mailers[] = $mailer;
+    } else {
+      $adapters = PhabricatorMailImplementationAdapter::getAllAdapters();
+      $next_priority = -1;
+
+      foreach ($config as $spec) {
+        $type = $spec['type'];
+        if (!isset($adapters[$type])) {
+          throw new Exception(
+            pht(
+              'Unknown mailer ("%s")!',
+              $type));
+        }
 
-    $mailers[] = $mailer;
+        $key = $spec['key'];
+        $mailer = id(clone $adapters[$type])
+          ->setKey($key);
 
-    return $mailers;
+        $priority = idx($spec, 'priority');
+        if (!$priority) {
+          $priority = $next_priority;
+          $next_priority--;
+        }
+        $mailer->setPriority($priority);
+
+        $defaults = $mailer->newDefaultOptions();
+        $options = idx($spec, 'options', array()) + $defaults;
+        $mailer->setOptions($options);
+      }
+    }
+
+    $sorted = array();
+    $groups = mgroup($mailers, 'getPriority');
+    ksort($groups);
+    foreach ($groups as $group) {
+      // Reorder services within the same priority group randomly.
+      shuffle($group);
+      foreach ($group as $mailer) {
+        $sorted[] = $mailer;
+      }
+    }
+
+    foreach ($sorted as $mailer) {
+      $mailer->prepareForSend();
+    }
+
+    return $sorted;
   }
 
   public function sendWithMailers(array $mailers) {
+    if (!$mailers) {
+      return $this
+        ->setStatus(PhabricatorMailOutboundStatus::STATUS_VOID)
+        ->setMessage(pht('No mailers are configured.'))
+        ->save();
+    }
+
     $exceptions = array();
     foreach ($mailers as $template_mailer) {
       $mailer = null;
@@ -865,6 +917,12 @@
       $mailer->addCCs($add_cc);
     }
 
+    // Keep track of which mailer actually ended up accepting the message.
+    $mailer_key = $mailer->getKey();
+    if ($mailer_key !== null) {
+      $this->setParam('mailer.key', $mailer_key);
+    }
+
     return $mailer;
   }
 
diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
--- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
+++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
@@ -2575,12 +2575,13 @@
 
         $mail = $this->buildMailForTarget($object, $xactions, $target);
 
-        if ($this->mustEncrypt) {
-          $mail
-            ->setMustEncrypt(true)
-            ->setMustEncryptReasons($this->mustEncrypt);
+        if ($mail) {
+          if ($this->mustEncrypt) {
+            $mail
+              ->setMustEncrypt(true)
+              ->setMustEncryptReasons($this->mustEncrypt);
+          }
         }
-
       } catch (Exception $ex) {
         $caught = $ex;
       }