diff --git a/resources/sql/autopatches/20150611.spaces.1.mailxaction.sql b/resources/sql/autopatches/20150611.spaces.1.mailxaction.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20150611.spaces.1.mailxaction.sql
@@ -0,0 +1,19 @@
+CREATE TABLE {$NAMESPACE}_metamta.metamta_applicationemailtransaction (
+  id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+  phid VARBINARY(64) NOT NULL,
+  authorPHID VARBINARY(64) NOT NULL,
+  objectPHID VARBINARY(64) NOT NULL,
+  viewPolicy VARBINARY(64) NOT NULL,
+  editPolicy VARBINARY(64) NOT NULL,
+  commentPHID VARBINARY(64) DEFAULT NULL,
+  commentVersion INT UNSIGNED NOT NULL,
+  transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL,
+  oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
+  newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
+  contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
+  metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
+  dateCreated INT UNSIGNED NOT NULL,
+  dateModified INT UNSIGNED NOT NULL,
+  UNIQUE KEY `key_phid` (`phid`),
+  KEY `key_object` (`objectPHID`)
+) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -2051,9 +2051,12 @@
     'PhabricatorMetaMTAApplication' => 'applications/metamta/application/PhabricatorMetaMTAApplication.php',
     'PhabricatorMetaMTAApplicationEmail' => 'applications/metamta/storage/PhabricatorMetaMTAApplicationEmail.php',
     'PhabricatorMetaMTAApplicationEmailDatasource' => 'applications/metamta/typeahead/PhabricatorMetaMTAApplicationEmailDatasource.php',
+    'PhabricatorMetaMTAApplicationEmailEditor' => 'applications/metamta/editor/PhabricatorMetaMTAApplicationEmailEditor.php',
     'PhabricatorMetaMTAApplicationEmailPHIDType' => 'applications/phid/PhabricatorMetaMTAApplicationEmailPHIDType.php',
     'PhabricatorMetaMTAApplicationEmailPanel' => 'applications/metamta/applicationpanel/PhabricatorMetaMTAApplicationEmailPanel.php',
     'PhabricatorMetaMTAApplicationEmailQuery' => 'applications/metamta/query/PhabricatorMetaMTAApplicationEmailQuery.php',
+    'PhabricatorMetaMTAApplicationEmailTransaction' => 'applications/metamta/storage/PhabricatorMetaMTAApplicationEmailTransaction.php',
+    'PhabricatorMetaMTAApplicationEmailTransactionQuery' => 'applications/metamta/query/PhabricatorMetaMTAApplicationEmailTransactionQuery.php',
     'PhabricatorMetaMTAAttachment' => 'applications/metamta/storage/PhabricatorMetaMTAAttachment.php',
     'PhabricatorMetaMTAConfigOptions' => 'applications/config/option/PhabricatorMetaMTAConfigOptions.php',
     'PhabricatorMetaMTAController' => 'applications/metamta/controller/PhabricatorMetaMTAController.php',
@@ -5493,11 +5496,16 @@
     'PhabricatorMetaMTAApplicationEmail' => array(
       'PhabricatorMetaMTADAO',
       'PhabricatorPolicyInterface',
+      'PhabricatorApplicationTransactionInterface',
+      'PhabricatorDestructibleInterface',
     ),
     'PhabricatorMetaMTAApplicationEmailDatasource' => 'PhabricatorTypeaheadDatasource',
+    'PhabricatorMetaMTAApplicationEmailEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhabricatorMetaMTAApplicationEmailPHIDType' => 'PhabricatorPHIDType',
     'PhabricatorMetaMTAApplicationEmailPanel' => 'PhabricatorApplicationConfigurationPanel',
     'PhabricatorMetaMTAApplicationEmailQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+    'PhabricatorMetaMTAApplicationEmailTransaction' => 'PhabricatorApplicationTransaction',
+    'PhabricatorMetaMTAApplicationEmailTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhabricatorMetaMTAConfigOptions' => 'PhabricatorApplicationConfigOptions',
     'PhabricatorMetaMTAController' => 'PhabricatorController',
     'PhabricatorMetaMTADAO' => 'PhabricatorLiskDAO',
diff --git a/src/applications/metamta/applicationpanel/PhabricatorMetaMTAApplicationEmailPanel.php b/src/applications/metamta/applicationpanel/PhabricatorMetaMTAApplicationEmailPanel.php
--- a/src/applications/metamta/applicationpanel/PhabricatorMetaMTAApplicationEmailPanel.php
+++ b/src/applications/metamta/applicationpanel/PhabricatorMetaMTAApplicationEmailPanel.php
@@ -190,30 +190,6 @@
       ));
   }
 
-  private function validateApplicationEmail($email) {
-    $errors = array();
-    $e_email = true;
-
-    if (!strlen($email)) {
-      $e_email = pht('Required');
-      $errors[] = pht('Email is required.');
-    } else if (!PhabricatorUserEmail::isValidAddress($email)) {
-      $e_email = pht('Invalid');
-      $errors[] = PhabricatorUserEmail::describeValidAddresses();
-    } else if (!PhabricatorUserEmail::isAllowedAddress($email)) {
-      $e_email = pht('Disallowed');
-      $errors[] = PhabricatorUserEmail::describeAllowedAddresses();
-    }
-    $user_emails = id(new PhabricatorUserEmail())
-      ->loadAllWhere('address = %s', $email);
-    if ($user_emails) {
-      $e_email = pht('Duplicate');
-      $errors[] = pht('A user already has this email.');
-    }
-
-    return array($e_email, $errors);
-  }
-
   private function returnNewAddressResponse(
     AphrontRequest $request,
     PhutilURI $uri,
@@ -265,45 +241,59 @@
 
     $viewer = $request->getUser();
 
-    $e_email = true;
-    $email   = null;
-    $errors  = array();
-    $default_user_key =
+    $config_default =
       PhabricatorMetaMTAApplicationEmail::CONFIG_DEFAULT_AUTHOR;
+
+    $e_email = true;
+    $v_email = $email_object->getAddress();
+    $v_default = $email_object->getConfigValue($config_default);
+
+    $validation_exception = null;
     if ($request->isDialogFormPost()) {
-      $email = trim($request->getStr('email'));
-      list($e_email, $errors) = $this->validateApplicationEmail($email);
-      $email_object->setAddress($email);
-      $default_user = $request->getArr($default_user_key);
-      $default_user = reset($default_user);
-      if ($default_user) {
-        $email_object->setConfigValue($default_user_key, $default_user);
-      }
+      $e_email = null;
 
-      if (!$errors) {
-        try {
-          $email_object->save();
-          return id(new AphrontRedirectResponse())->setURI(
-            $uri->alter('highlight', $email_object->getID()));
-        } catch (AphrontDuplicateKeyQueryException $ex) {
-          $e_email = pht('Duplicate');
-          $errors[] = pht(
-            'Another application is already configured to use this email '.
-            'address.');
-        }
-      }
-    }
+      $v_email = trim($request->getStr('email'));
+      $v_default = $request->getArr($config_default);
+      $v_default = nonempty(head($v_default), null);
 
-    if ($errors) {
-      $errors = id(new PHUIInfoView())
-        ->setErrors($errors);
+      $type_address =
+        PhabricatorMetaMTAApplicationEmailTransaction::TYPE_ADDRESS;
+      $type_config =
+        PhabricatorMetaMTAApplicationEmailTransaction::TYPE_CONFIG;
+
+      $key_config = PhabricatorMetaMTAApplicationEmailTransaction::KEY_CONFIG;
+
+      $xactions = array();
+
+      $xactions[] = id(new PhabricatorMetaMTAApplicationEmailTransaction())
+        ->setTransactionType($type_address)
+        ->setNewValue($v_email);
+
+      $xactions[] = id(new PhabricatorMetaMTAApplicationEmailTransaction())
+        ->setTransactionType($type_config)
+        ->setMetadataValue($key_config, $config_default)
+        ->setNewValue($v_default);
+
+      $editor = id(new PhabricatorMetaMTAApplicationEmailEditor())
+        ->setActor($viewer)
+        ->setContentSourceFromRequest($request)
+        ->setContinueOnNoEffect(true);
+
+      try {
+        $editor->applyTransactions($email_object, $xactions);
+
+        return id(new AphrontRedirectResponse())->setURI(
+          $uri->alter('highlight', $email_object->getID()));
+      } catch (PhabricatorApplicationTransactionValidationException $ex) {
+        $validation_exception = $ex;
+        $e_email = $ex->getShortMessage($type_address);
+      }
     }
 
-    $default_user = $email_object->getConfigValue($default_user_key);
-    if ($default_user) {
-      $default_user_value = array($default_user);
+    if ($v_default) {
+      $v_default = array($v_default);
     } else {
-      $default_user_value = array();
+      $v_default = array();
     }
 
     $form = id(new AphrontFormView())
@@ -312,28 +302,29 @@
         id(new AphrontFormTextControl())
           ->setLabel(pht('Email'))
           ->setName('email')
-          ->setValue($email_object->getAddress())
-          ->setCaption(PhabricatorUserEmail::describeAllowedAddresses())
+          ->setValue($v_email)
           ->setError($e_email))
       ->appendControl(
         id(new AphrontFormTokenizerControl())
           ->setDatasource(new PhabricatorPeopleDatasource())
           ->setLabel(pht('Default Author'))
-          ->setName($default_user_key)
+          ->setName($config_default)
           ->setLimit(1)
-          ->setValue($default_user_value)
+          ->setValue($v_default)
           ->setCaption(pht(
             'Used if the "From:" address does not map to a known account.')));
+
     if ($is_new) {
       $title = pht('New Address');
     } else {
       $title = pht('Edit Address');
     }
+
     $dialog = id(new AphrontDialogView())
       ->setUser($viewer)
       ->setWidth(AphrontDialogView::WIDTH_FORM)
       ->setTitle($title)
-      ->appendChild($errors)
+      ->setValidationException($validation_exception)
       ->appendForm($form)
       ->addSubmitButton(pht('Save'))
       ->addCancelButton($uri);
@@ -350,7 +341,8 @@
     PhutilURI $uri,
     $email_object_id) {
 
-    $viewer = $request->getUser();
+    $viewer = $this->getViewer();
+
     $email_object = id(new PhabricatorMetaMTAApplicationEmailQuery())
       ->setViewer($viewer)
       ->withIDs(array($email_object_id))
@@ -365,7 +357,8 @@
     }
 
     if ($request->isDialogFormPost()) {
-      $email_object->delete();
+      $engine = new PhabricatorDestructionEngine();
+      $engine->destroyObject($email_object);
       return id(new AphrontRedirectResponse())->setURI($uri);
     }
 
diff --git a/src/applications/metamta/editor/PhabricatorMetaMTAApplicationEmailEditor.php b/src/applications/metamta/editor/PhabricatorMetaMTAApplicationEmailEditor.php
new file mode 100644
--- /dev/null
+++ b/src/applications/metamta/editor/PhabricatorMetaMTAApplicationEmailEditor.php
@@ -0,0 +1,145 @@
+<?php
+
+final class PhabricatorMetaMTAApplicationEmailEditor
+  extends PhabricatorApplicationTransactionEditor {
+
+  public function getEditorApplicationClass() {
+    return pht('PhabricatorMetaMTAApplication');
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Application Emails');
+  }
+
+  public function getTransactionTypes() {
+    $types = parent::getTransactionTypes();
+
+    $types[] = PhabricatorMetaMTAApplicationEmailTransaction::TYPE_ADDRESS;
+    $types[] = PhabricatorMetaMTAApplicationEmailTransaction::TYPE_CONFIG;
+
+    return $types;
+  }
+
+  protected function getCustomTransactionOldValue(
+    PhabricatorLiskDAO $object,
+    PhabricatorApplicationTransaction $xaction) {
+
+    switch ($xaction->getTransactionType()) {
+      case PhabricatorMetaMTAApplicationEmailTransaction::TYPE_ADDRESS:
+        return $object->getAddress();
+      case PhabricatorMetaMTAApplicationEmailTransaction::TYPE_CONFIG:
+        $key = $xaction->getMetadataValue(
+          PhabricatorMetaMTAApplicationEmailTransaction::KEY_CONFIG);
+        return $object->getConfigValue($key);
+    }
+
+    return parent::getCustomTransactionOldValue($object, $xaction);
+  }
+
+  protected function getCustomTransactionNewValue(
+    PhabricatorLiskDAO $object,
+    PhabricatorApplicationTransaction $xaction) {
+
+    switch ($xaction->getTransactionType()) {
+      case PhabricatorMetaMTAApplicationEmailTransaction::TYPE_ADDRESS:
+      case PhabricatorMetaMTAApplicationEmailTransaction::TYPE_CONFIG:
+        return $xaction->getNewValue();
+    }
+
+    return parent::getCustomTransactionNewValue($object, $xaction);
+  }
+
+  protected function applyCustomInternalTransaction(
+    PhabricatorLiskDAO $object,
+    PhabricatorApplicationTransaction $xaction) {
+
+    $new = $xaction->getNewValue();
+
+    switch ($xaction->getTransactionType()) {
+      case PhabricatorMetaMTAApplicationEmailTransaction::TYPE_ADDRESS:
+        $object->setAddress($new);
+        return;
+      case PhabricatorMetaMTAApplicationEmailTransaction::TYPE_CONFIG:
+        $key = $xaction->getMetadataValue(
+          PhabricatorMetaMTAApplicationEmailTransaction::KEY_CONFIG);
+        $object->setConfigValue($key, $new);
+        return;
+    }
+
+    return parent::applyCustomInternalTransaction($object, $xaction);
+  }
+
+  protected function applyCustomExternalTransaction(
+    PhabricatorLiskDAO $object,
+    PhabricatorApplicationTransaction $xaction) {
+
+    switch ($xaction->getTransactionType()) {
+      case PhabricatorMetaMTAApplicationEmailTransaction::TYPE_ADDRESS:
+      case PhabricatorMetaMTAApplicationEmailTransaction::TYPE_CONFIG:
+        return;
+    }
+
+    return parent::applyCustomExternalTransaction($object, $xaction);
+  }
+
+  protected function validateTransaction(
+    PhabricatorLiskDAO $object,
+    $type,
+    array $xactions) {
+
+    $errors = parent::validateTransaction($object, $type, $xactions);
+
+    switch ($type) {
+      case PhabricatorMetaMTAApplicationEmailTransaction::TYPE_ADDRESS:
+        foreach ($xactions as $xaction) {
+          $email = $xaction->getNewValue();
+          if (!strlen($email)) {
+            // We'll deal with this below.
+            continue;
+          }
+
+          if (!PhabricatorUserEmail::isValidAddress($email)) {
+            $errors[] = new PhabricatorApplicationTransactionValidationError(
+              $type,
+              pht('Invalid'),
+              pht('Email address is not formatted properly.'));
+          }
+        }
+
+        $missing = $this->validateIsEmptyTextField(
+          $object->getAddress(),
+          $xactions);
+
+        if ($missing) {
+          $error = new PhabricatorApplicationTransactionValidationError(
+            $type,
+            pht('Required'),
+            pht('You must provide an email address.'),
+            nonempty(last($xactions), null));
+
+          $error->setIsMissingFieldError(true);
+          $errors[] = $error;
+        }
+        break;
+    }
+
+    return $errors;
+  }
+
+  protected function didCatchDuplicateKeyException(
+    PhabricatorLiskDAO $object,
+    array $xactions,
+    Exception $ex) {
+
+    $errors = array();
+    $errors[] = new PhabricatorApplicationTransactionValidationError(
+      PhabricatorMetaMTAApplicationEmailTransaction::TYPE_ADDRESS,
+      pht('Duplicate'),
+      pht('This email address is already in use.'),
+      null);
+
+    throw new PhabricatorApplicationTransactionValidationException($errors);
+  }
+
+
+}
diff --git a/src/applications/metamta/query/PhabricatorMetaMTAApplicationEmailTransactionQuery.php b/src/applications/metamta/query/PhabricatorMetaMTAApplicationEmailTransactionQuery.php
new file mode 100644
--- /dev/null
+++ b/src/applications/metamta/query/PhabricatorMetaMTAApplicationEmailTransactionQuery.php
@@ -0,0 +1,10 @@
+<?php
+
+final class PhabricatorMetaMTAApplicationEmailTransactionQuery
+  extends PhabricatorApplicationTransactionQuery {
+
+  public function getTemplateApplicationTransaction() {
+    return new PhabricatorMetaMTAApplicationEmailTransaction();
+  }
+
+}
diff --git a/src/applications/metamta/storage/PhabricatorMetaMTAApplicationEmail.php b/src/applications/metamta/storage/PhabricatorMetaMTAApplicationEmail.php
--- a/src/applications/metamta/storage/PhabricatorMetaMTAApplicationEmail.php
+++ b/src/applications/metamta/storage/PhabricatorMetaMTAApplicationEmail.php
@@ -2,7 +2,10 @@
 
 final class PhabricatorMetaMTAApplicationEmail
   extends PhabricatorMetaMTADAO
-  implements PhabricatorPolicyInterface {
+  implements
+    PhabricatorPolicyInterface,
+    PhabricatorApplicationTransactionInterface,
+    PhabricatorDestructibleInterface {
 
   protected $applicationPHID;
   protected $address;
@@ -109,4 +112,35 @@
     return $this->getApplication()->describeAutomaticCapability($capability);
   }
 
+
+/* -(  PhabricatorApplicationTransactionInterface  )------------------------- */
+
+
+  public function getApplicationTransactionEditor() {
+    return new PhabricatorMetaMTAApplicationEmailEditor();
+  }
+
+  public function getApplicationTransactionObject() {
+    return $this;
+  }
+
+  public function getApplicationTransactionTemplate() {
+    return new PhabricatorMetaMTAApplicationEmailTransaction();
+  }
+
+  public function willRenderTimeline(
+    PhabricatorApplicationTransactionView $timeline,
+    AphrontRequest $request) {
+    return $timeline;
+  }
+
+
+/* -(  PhabricatorDestructibleInterface  )----------------------------------- */
+
+
+  public function destroyObjectPermanently(
+    PhabricatorDestructionEngine $engine) {
+    $this->delete();
+  }
+
 }
diff --git a/src/applications/metamta/storage/PhabricatorMetaMTAApplicationEmailTransaction.php b/src/applications/metamta/storage/PhabricatorMetaMTAApplicationEmailTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/metamta/storage/PhabricatorMetaMTAApplicationEmailTransaction.php
@@ -0,0 +1,23 @@
+<?php
+
+final class PhabricatorMetaMTAApplicationEmailTransaction
+  extends PhabricatorApplicationTransaction {
+
+  const KEY_CONFIG = 'appemail.config.key';
+
+  const TYPE_ADDRESS = 'appemail.address';
+  const TYPE_CONFIG = 'appemail.config';
+
+  public function getApplicationName() {
+    return 'metamta';
+  }
+
+  public function getApplicationTransactionType() {
+    return PhabricatorMetaMTAApplicationEmailPHIDType::TYPECONST;
+  }
+
+  public function getApplicationTransactionCommentObject() {
+    return null;
+  }
+
+}
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
@@ -844,6 +844,11 @@
         $object->save();
       } catch (AphrontDuplicateKeyQueryException $ex) {
         $object->killTransaction();
+
+        // This callback has an opportunity to throw a better exception,
+        // so execution may end here.
+        $this->didCatchDuplicateKeyException($object, $xactions, $ex);
+
         throw $ex;
       }
 
@@ -1021,6 +1026,13 @@
     return $xactions;
   }
 
+  protected function didCatchDuplicateKeyException(
+    PhabricatorLiskDAO $object,
+    array $xactions,
+    Exception $ex) {
+    return;
+  }
+
   public function publishTransactions(
     PhabricatorLiskDAO $object,
     array $xactions) {