diff --git a/src/applications/audit/editor/PhabricatorAuditEditor.php b/src/applications/audit/editor/PhabricatorAuditEditor.php
--- a/src/applications/audit/editor/PhabricatorAuditEditor.php
+++ b/src/applications/audit/editor/PhabricatorAuditEditor.php
@@ -3,6 +3,14 @@
 final class PhabricatorAuditEditor
   extends PhabricatorApplicationTransactionEditor {
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorAuditApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Audits');
+  }
+
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
diff --git a/src/applications/auth/editor/PhabricatorAuthProviderConfigEditor.php b/src/applications/auth/editor/PhabricatorAuthProviderConfigEditor.php
--- a/src/applications/auth/editor/PhabricatorAuthProviderConfigEditor.php
+++ b/src/applications/auth/editor/PhabricatorAuthProviderConfigEditor.php
@@ -3,6 +3,14 @@
 final class PhabricatorAuthProviderConfigEditor
   extends PhabricatorApplicationTransactionEditor {
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorAuthApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Auth Providers');
+  }
+
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
diff --git a/src/applications/config/editor/PhabricatorConfigEditor.php b/src/applications/config/editor/PhabricatorConfigEditor.php
--- a/src/applications/config/editor/PhabricatorConfigEditor.php
+++ b/src/applications/config/editor/PhabricatorConfigEditor.php
@@ -3,6 +3,14 @@
 final class PhabricatorConfigEditor
   extends PhabricatorApplicationTransactionEditor {
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorConfigApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Phabricator Configuration');
+  }
+
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
diff --git a/src/applications/conpherence/editor/ConpherenceEditor.php b/src/applications/conpherence/editor/ConpherenceEditor.php
--- a/src/applications/conpherence/editor/ConpherenceEditor.php
+++ b/src/applications/conpherence/editor/ConpherenceEditor.php
@@ -5,6 +5,14 @@
   const ERROR_EMPTY_PARTICIPANTS = 'error-empty-participants';
   const ERROR_EMPTY_MESSAGE = 'error-empty-message';
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorConpherenceApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Conpherence Threads');
+  }
+
   public static function createConpherence(
     PhabricatorUser $creator,
     array $participant_phids,
diff --git a/src/applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php b/src/applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php
--- a/src/applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php
+++ b/src/applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php
@@ -3,6 +3,14 @@
 final class PhabricatorDashboardPanelTransactionEditor
   extends PhabricatorApplicationTransactionEditor {
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorDashboardApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Dashboard Panels');
+  }
+
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
diff --git a/src/applications/dashboard/editor/PhabricatorDashboardTransactionEditor.php b/src/applications/dashboard/editor/PhabricatorDashboardTransactionEditor.php
--- a/src/applications/dashboard/editor/PhabricatorDashboardTransactionEditor.php
+++ b/src/applications/dashboard/editor/PhabricatorDashboardTransactionEditor.php
@@ -3,6 +3,14 @@
 final class PhabricatorDashboardTransactionEditor
   extends PhabricatorApplicationTransactionEditor {
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorDashboardApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Dashboards');
+  }
+
   public static function addPanelToDashboard(
     PhabricatorUser $actor,
     PhabricatorContentSource $content_source,
diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php
--- a/src/applications/differential/editor/DifferentialTransactionEditor.php
+++ b/src/applications/differential/editor/DifferentialTransactionEditor.php
@@ -7,6 +7,14 @@
   private $changedPriorToCommitURI;
   private $isCloseByCommit;
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorDifferentialApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Differential Revisions');
+  }
+
   public function getDiffUpdateTransaction(array $xactions) {
     $type_update = DifferentialTransaction::TYPE_UPDATE;
 
@@ -1192,6 +1200,25 @@
     return $body;
   }
 
+  public function getMailTagsMap() {
+    return array(
+      MetaMTANotificationType::TYPE_DIFFERENTIAL_REVIEW_REQUEST =>
+        pht('A revision is created.'),
+      MetaMTANotificationType::TYPE_DIFFERENTIAL_UPDATED =>
+        pht('A revision is updated.'),
+      MetaMTANotificationType::TYPE_DIFFERENTIAL_COMMENT =>
+        pht('Someone comments on a revision.'),
+      MetaMTANotificationType::TYPE_DIFFERENTIAL_CLOSED =>
+        pht('A revision is closed.'),
+      MetaMTANotificationType::TYPE_DIFFERENTIAL_REVIEWERS =>
+        pht("A revision's reviewers change."),
+      MetaMTANotificationType::TYPE_DIFFERENTIAL_CC =>
+        pht("A revision's CCs change."),
+      MetaMTANotificationType::TYPE_DIFFERENTIAL_OTHER =>
+        pht('Other revision activity not listed above occurs.'),
+    );
+  }
+
   protected function supportsSearch() {
     return true;
   }
diff --git a/src/applications/drydock/editor/DrydockBlueprintEditor.php b/src/applications/drydock/editor/DrydockBlueprintEditor.php
--- a/src/applications/drydock/editor/DrydockBlueprintEditor.php
+++ b/src/applications/drydock/editor/DrydockBlueprintEditor.php
@@ -3,6 +3,14 @@
 final class DrydockBlueprintEditor
   extends PhabricatorApplicationTransactionEditor {
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorDrydockApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Drydock Blueprints');
+  }
+
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
diff --git a/src/applications/files/editor/PhabricatorFileEditor.php b/src/applications/files/editor/PhabricatorFileEditor.php
--- a/src/applications/files/editor/PhabricatorFileEditor.php
+++ b/src/applications/files/editor/PhabricatorFileEditor.php
@@ -3,6 +3,14 @@
 final class PhabricatorFileEditor
   extends PhabricatorApplicationTransactionEditor {
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorFilesApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Files');
+  }
+
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
diff --git a/src/applications/harbormaster/editor/HarbormasterBuildPlanEditor.php b/src/applications/harbormaster/editor/HarbormasterBuildPlanEditor.php
--- a/src/applications/harbormaster/editor/HarbormasterBuildPlanEditor.php
+++ b/src/applications/harbormaster/editor/HarbormasterBuildPlanEditor.php
@@ -3,6 +3,14 @@
 final class HarbormasterBuildPlanEditor
   extends PhabricatorApplicationTransactionEditor {
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorHarbormasterApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Harbormaster Build Plans');
+  }
+
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
     $types[] = HarbormasterBuildPlanTransaction::TYPE_NAME;
diff --git a/src/applications/harbormaster/editor/HarbormasterBuildStepEditor.php b/src/applications/harbormaster/editor/HarbormasterBuildStepEditor.php
--- a/src/applications/harbormaster/editor/HarbormasterBuildStepEditor.php
+++ b/src/applications/harbormaster/editor/HarbormasterBuildStepEditor.php
@@ -3,6 +3,14 @@
 final class HarbormasterBuildStepEditor
   extends PhabricatorApplicationTransactionEditor {
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorHarbormasterApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Harbormaster Build Steps');
+  }
+
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
diff --git a/src/applications/harbormaster/editor/HarbormasterBuildTransactionEditor.php b/src/applications/harbormaster/editor/HarbormasterBuildTransactionEditor.php
--- a/src/applications/harbormaster/editor/HarbormasterBuildTransactionEditor.php
+++ b/src/applications/harbormaster/editor/HarbormasterBuildTransactionEditor.php
@@ -3,6 +3,14 @@
 final class HarbormasterBuildTransactionEditor
   extends PhabricatorApplicationTransactionEditor {
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorHarbormasterApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Harbormaster Builds');
+  }
+
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
diff --git a/src/applications/harbormaster/editor/HarbormasterBuildableTransactionEditor.php b/src/applications/harbormaster/editor/HarbormasterBuildableTransactionEditor.php
--- a/src/applications/harbormaster/editor/HarbormasterBuildableTransactionEditor.php
+++ b/src/applications/harbormaster/editor/HarbormasterBuildableTransactionEditor.php
@@ -3,6 +3,14 @@
 final class HarbormasterBuildableTransactionEditor
   extends PhabricatorApplicationTransactionEditor {
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorHarbormasterApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Harbormaster Buildables');
+  }
+
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
diff --git a/src/applications/herald/editor/HeraldRuleEditor.php b/src/applications/herald/editor/HeraldRuleEditor.php
--- a/src/applications/herald/editor/HeraldRuleEditor.php
+++ b/src/applications/herald/editor/HeraldRuleEditor.php
@@ -3,6 +3,14 @@
 final class HeraldRuleEditor
   extends PhabricatorApplicationTransactionEditor {
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorHeraldApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Herald Rules');
+  }
+
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
diff --git a/src/applications/legalpad/editor/LegalpadDocumentEditor.php b/src/applications/legalpad/editor/LegalpadDocumentEditor.php
--- a/src/applications/legalpad/editor/LegalpadDocumentEditor.php
+++ b/src/applications/legalpad/editor/LegalpadDocumentEditor.php
@@ -5,6 +5,14 @@
 
   private $isContribution = false;
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorLegalpadApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Legalpad Documents');
+  }
+
   private function setIsContribution($is_contribution) {
     $this->isContribution = $is_contribution;
   }
diff --git a/src/applications/macro/editor/PhabricatorMacroEditor.php b/src/applications/macro/editor/PhabricatorMacroEditor.php
--- a/src/applications/macro/editor/PhabricatorMacroEditor.php
+++ b/src/applications/macro/editor/PhabricatorMacroEditor.php
@@ -3,6 +3,14 @@
 final class PhabricatorMacroEditor
   extends PhabricatorApplicationTransactionEditor {
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorMacroApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Macros');
+  }
+
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
diff --git a/src/applications/maniphest/editor/ManiphestTransactionEditor.php b/src/applications/maniphest/editor/ManiphestTransactionEditor.php
--- a/src/applications/maniphest/editor/ManiphestTransactionEditor.php
+++ b/src/applications/maniphest/editor/ManiphestTransactionEditor.php
@@ -5,6 +5,14 @@
 
   private $heraldEmailPHIDs = array();
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorManiphestApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Maniphest Tasks');
+  }
+
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
@@ -428,6 +436,25 @@
     return $phids;
   }
 
+  public function getMailTagsMap() {
+    return array(
+      MetaMTANotificationType::TYPE_MANIPHEST_STATUS =>
+        pht("A task's status changes."),
+      MetaMTANotificationType::TYPE_MANIPHEST_OWNER =>
+        pht("A task's owner changes."),
+      MetaMTANotificationType::TYPE_MANIPHEST_PRIORITY =>
+        pht("A task's priority changes."),
+      MetaMTANotificationType::TYPE_MANIPHEST_CC =>
+        pht("A task's CCs change."),
+      MetaMTANotificationType::TYPE_MANIPHEST_PROJECTS =>
+        pht("A task's associated projects change."),
+      MetaMTANotificationType::TYPE_MANIPHEST_COMMENT =>
+        pht('Someone comments on a task.'),
+      MetaMTANotificationType::TYPE_MANIPHEST_OTHER =>
+        pht('Other task activity not listed above occurs.'),
+    );
+  }
+
   protected function buildReplyHandler(PhabricatorLiskDAO $object) {
     return id(new ManiphestReplyHandler())
       ->setMailReceiver($object);
diff --git a/src/applications/nuance/editor/NuanceItemEditor.php b/src/applications/nuance/editor/NuanceItemEditor.php
--- a/src/applications/nuance/editor/NuanceItemEditor.php
+++ b/src/applications/nuance/editor/NuanceItemEditor.php
@@ -3,6 +3,14 @@
 final class NuanceItemEditor
   extends PhabricatorApplicationTransactionEditor {
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorNuanceApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Nuance Items');
+  }
+
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
diff --git a/src/applications/nuance/editor/NuanceQueueEditor.php b/src/applications/nuance/editor/NuanceQueueEditor.php
--- a/src/applications/nuance/editor/NuanceQueueEditor.php
+++ b/src/applications/nuance/editor/NuanceQueueEditor.php
@@ -3,6 +3,14 @@
 final class NuanceQueueEditor
   extends PhabricatorApplicationTransactionEditor {
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorNuanceApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Nuance Queues');
+  }
+
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
diff --git a/src/applications/nuance/editor/NuanceRequestorEditor.php b/src/applications/nuance/editor/NuanceRequestorEditor.php
--- a/src/applications/nuance/editor/NuanceRequestorEditor.php
+++ b/src/applications/nuance/editor/NuanceRequestorEditor.php
@@ -3,6 +3,14 @@
 final class NuanceRequestorEditor
   extends PhabricatorApplicationTransactionEditor {
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorNuanceApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Nuance Requestors');
+  }
+
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
diff --git a/src/applications/nuance/editor/NuanceSourceEditor.php b/src/applications/nuance/editor/NuanceSourceEditor.php
--- a/src/applications/nuance/editor/NuanceSourceEditor.php
+++ b/src/applications/nuance/editor/NuanceSourceEditor.php
@@ -3,6 +3,14 @@
 final class NuanceSourceEditor
   extends PhabricatorApplicationTransactionEditor {
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorNuanceApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Nuance Sources');
+  }
+
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
diff --git a/src/applications/passphrase/editor/PassphraseCredentialTransactionEditor.php b/src/applications/passphrase/editor/PassphraseCredentialTransactionEditor.php
--- a/src/applications/passphrase/editor/PassphraseCredentialTransactionEditor.php
+++ b/src/applications/passphrase/editor/PassphraseCredentialTransactionEditor.php
@@ -3,6 +3,14 @@
 final class PassphraseCredentialTransactionEditor
   extends PhabricatorApplicationTransactionEditor {
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorPassphraseApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Passphrase Credentials');
+  }
+
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
diff --git a/src/applications/paste/editor/PhabricatorPasteEditor.php b/src/applications/paste/editor/PhabricatorPasteEditor.php
--- a/src/applications/paste/editor/PhabricatorPasteEditor.php
+++ b/src/applications/paste/editor/PhabricatorPasteEditor.php
@@ -5,6 +5,14 @@
 
   private $pasteFile;
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorPasteApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Pastes');
+  }
+
   public static function initializeFileForPaste(
     PhabricatorUser $actor,
     $name,
diff --git a/src/applications/people/editor/PhabricatorUserProfileEditor.php b/src/applications/people/editor/PhabricatorUserProfileEditor.php
--- a/src/applications/people/editor/PhabricatorUserProfileEditor.php
+++ b/src/applications/people/editor/PhabricatorUserProfileEditor.php
@@ -3,5 +3,13 @@
 final class PhabricatorUserProfileEditor
   extends PhabricatorApplicationTransactionEditor {
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorPeopleApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('User Profiles');
+  }
+
 
 }
diff --git a/src/applications/phlux/editor/PhluxVariableEditor.php b/src/applications/phlux/editor/PhluxVariableEditor.php
--- a/src/applications/phlux/editor/PhluxVariableEditor.php
+++ b/src/applications/phlux/editor/PhluxVariableEditor.php
@@ -3,6 +3,14 @@
 final class PhluxVariableEditor
   extends PhabricatorApplicationTransactionEditor {
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorPhluxApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Phlux Variables');
+  }
+
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
     $types[] = PhluxTransaction::TYPE_EDIT_KEY;
diff --git a/src/applications/pholio/editor/PholioMockEditor.php b/src/applications/pholio/editor/PholioMockEditor.php
--- a/src/applications/pholio/editor/PholioMockEditor.php
+++ b/src/applications/pholio/editor/PholioMockEditor.php
@@ -3,6 +3,15 @@
 final class PholioMockEditor extends PhabricatorApplicationTransactionEditor {
 
   private $newImages = array();
+
+  public function getEditorApplicationClass() {
+    return 'PhabricatorPholioApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Pholio Mocks');
+  }
+
   private function setNewImages(array $new_images) {
     assert_instances_of($new_images, 'PholioImage');
     $this->newImages = $new_images;
@@ -411,6 +420,19 @@
     return PhabricatorEnv::getEnvConfig('metamta.pholio.subject-prefix');
   }
 
+  public function getMailTagsMap() {
+    return array(
+      MetaMTANotificationType::TYPE_PHOLIO_STATUS =>
+        pht("A mock's status changes."),
+      MetaMTANotificationType::TYPE_PHOLIO_COMMENT =>
+        pht('Someone comments on a mock.'),
+      MetaMTANotificationType::TYPE_PHOLIO_UPDATED =>
+        pht('Mock images or descriptions change.'),
+      MetaMTANotificationType::TYPE_PHOLIO_OTHER =>
+        pht('Other mock activity not listed above occurs.'),
+    );
+  }
+
   protected function shouldPublishFeedStory(
     PhabricatorLiskDAO $object,
     array $xactions) {
diff --git a/src/applications/phortune/editor/PhortuneAccountEditor.php b/src/applications/phortune/editor/PhortuneAccountEditor.php
--- a/src/applications/phortune/editor/PhortuneAccountEditor.php
+++ b/src/applications/phortune/editor/PhortuneAccountEditor.php
@@ -4,6 +4,14 @@
 final class PhortuneAccountEditor
   extends PhabricatorApplicationTransactionEditor {
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorPhortuneApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Phortune Accounts');
+  }
+
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
diff --git a/src/applications/phortune/editor/PhortuneProductEditor.php b/src/applications/phortune/editor/PhortuneProductEditor.php
--- a/src/applications/phortune/editor/PhortuneProductEditor.php
+++ b/src/applications/phortune/editor/PhortuneProductEditor.php
@@ -4,6 +4,14 @@
 final class PhortuneProductEditor
   extends PhabricatorApplicationTransactionEditor {
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorPhortuneApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Phortune Products');
+  }
+
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
diff --git a/src/applications/ponder/editor/PonderAnswerEditor.php b/src/applications/ponder/editor/PonderAnswerEditor.php
--- a/src/applications/ponder/editor/PonderAnswerEditor.php
+++ b/src/applications/ponder/editor/PonderAnswerEditor.php
@@ -2,6 +2,10 @@
 
 final class PonderAnswerEditor extends PonderEditor {
 
+  public function getEditorObjectsDescription() {
+    return pht('Ponder Answers');
+  }
+
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
diff --git a/src/applications/ponder/editor/PonderEditor.php b/src/applications/ponder/editor/PonderEditor.php
--- a/src/applications/ponder/editor/PonderEditor.php
+++ b/src/applications/ponder/editor/PonderEditor.php
@@ -3,6 +3,10 @@
 abstract class PonderEditor
   extends PhabricatorApplicationTransactionEditor {
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorPonderApplication';
+  }
+
   protected function shouldPublishFeedStory(
     PhabricatorLiskDAO $object,
     array $xactions) {
diff --git a/src/applications/ponder/editor/PonderQuestionEditor.php b/src/applications/ponder/editor/PonderQuestionEditor.php
--- a/src/applications/ponder/editor/PonderQuestionEditor.php
+++ b/src/applications/ponder/editor/PonderQuestionEditor.php
@@ -5,6 +5,10 @@
 
   private $answer;
 
+  public function getEditorObjectsDescription() {
+    return pht('Ponder Questions');
+  }
+
   /**
    * This is used internally on @{method:applyInitialEffects} if a transaction
    * of type PonderQuestionTransaction::TYPE_ANSWERS is in the mix. The value
diff --git a/src/applications/project/editor/PhabricatorProjectColumnTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectColumnTransactionEditor.php
--- a/src/applications/project/editor/PhabricatorProjectColumnTransactionEditor.php
+++ b/src/applications/project/editor/PhabricatorProjectColumnTransactionEditor.php
@@ -3,6 +3,14 @@
 final class PhabricatorProjectColumnTransactionEditor
   extends PhabricatorApplicationTransactionEditor {
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorProjectApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Workboard Columns');
+  }
+
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php
--- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php
+++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php
@@ -3,6 +3,14 @@
 final class PhabricatorProjectTransactionEditor
   extends PhabricatorApplicationTransactionEditor {
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorProjectApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Projects');
+  }
+
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
diff --git a/src/applications/releeph/editor/ReleephProductEditor.php b/src/applications/releeph/editor/ReleephProductEditor.php
--- a/src/applications/releeph/editor/ReleephProductEditor.php
+++ b/src/applications/releeph/editor/ReleephProductEditor.php
@@ -3,6 +3,14 @@
 final class ReleephProductEditor
   extends PhabricatorApplicationTransactionEditor {
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorReleephApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Releeph Products');
+  }
+
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
diff --git a/src/applications/releeph/editor/ReleephRequestTransactionalEditor.php b/src/applications/releeph/editor/ReleephRequestTransactionalEditor.php
--- a/src/applications/releeph/editor/ReleephRequestTransactionalEditor.php
+++ b/src/applications/releeph/editor/ReleephRequestTransactionalEditor.php
@@ -3,6 +3,14 @@
 final class ReleephRequestTransactionalEditor
   extends PhabricatorApplicationTransactionEditor {
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorReleephApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Releeph Requests');
+  }
+
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
diff --git a/src/applications/repository/editor/PhabricatorRepositoryEditor.php b/src/applications/repository/editor/PhabricatorRepositoryEditor.php
--- a/src/applications/repository/editor/PhabricatorRepositoryEditor.php
+++ b/src/applications/repository/editor/PhabricatorRepositoryEditor.php
@@ -3,6 +3,14 @@
 final class PhabricatorRepositoryEditor
   extends PhabricatorApplicationTransactionEditor {
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorDiffusionApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Repositories');
+  }
+
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
diff --git a/src/applications/settings/panel/PhabricatorSettingsPanelEmailPreferences.php b/src/applications/settings/panel/PhabricatorSettingsPanelEmailPreferences.php
--- a/src/applications/settings/panel/PhabricatorSettingsPanelEmailPreferences.php
+++ b/src/applications/settings/panel/PhabricatorSettingsPanelEmailPreferences.php
@@ -35,17 +35,7 @@
 
       $new_tags = $request->getArr('mailtags');
       $mailtags = $preferences->getPreference('mailtags', array());
-      $all_tags = $this->getMailTags();
-
-      $maniphest = 'PhabricatorManiphestApplication';
-      if (!PhabricatorApplication::isClassInstalled($maniphest)) {
-        $all_tags = array_diff_key($all_tags, $this->getManiphestMailTags());
-      }
-
-      $pholio = 'PhabricatorPholioApplication';
-      if (!PhabricatorApplication::isClassInstalled($pholio)) {
-        $all_tags = array_diff_key($all_tags, $this->getPholioMailTags());
-      }
+      $all_tags = $this->getAllTags($user);
 
       foreach ($all_tags as $key => $label) {
         $mailtags[$key] = (bool)idx($new_tags, $key, false);
@@ -125,34 +115,51 @@
         'CC\'d on). To receive email alerts when other objects are created, '.
         'configure [[ /herald/ | Herald Rules ]].'.
         "\n\n".
-        '**Phabricator will send an email to your primary account when:**'));
-
-    if (PhabricatorApplication::isClassInstalledForViewer(
-      'PhabricatorDifferentialApplication', $user)) {
-      $form
-        ->appendChild(
-          $this->buildMailTagCheckboxes(
-            $this->getDifferentialMailTags(),
-            $mailtags)
-            ->setLabel(pht('Differential')));
+        'Phabricator will send an email to your primary account when:'));
+
+    $editors = $this->getAllEditorsWithTags($user);
+
+    // Find all the tags shared by more than one application, and put them
+    // in a "common" group.
+    $all_tags = array();
+    foreach ($editors as $editor) {
+      foreach ($editor->getMailTagsMap() as $tag => $name) {
+        if (empty($all_tags[$tag])) {
+          $all_tags[$tag] = array(
+            'count' => 0,
+            'name' => $name,
+          );
+        }
+        $all_tags[$tag]['count'];
+      }
     }
 
-    if (PhabricatorApplication::isClassInstalledForViewer(
-      'PhabricatorManiphestApplication', $user)) {
-      $form->appendChild(
-        $this->buildMailTagCheckboxes(
-          $this->getManiphestMailTags(),
-          $mailtags)
-          ->setLabel(pht('Maniphest')));
+    $common_tags = array();
+    foreach ($all_tags as $tag => $info) {
+      if ($info['count'] > 1) {
+        $common_tags[$tag] = $info['name'];
+      }
+    }
+
+    // Build up the groups of application-specific options.
+    $tag_groups = array();
+    foreach ($editors as $editor) {
+      $tag_groups[] = array(
+        $editor->getEditorObjectsDescription(),
+        array_diff_key($editor->getMailTagsMap(), $common_tags));
     }
 
-    if (PhabricatorApplication::isClassInstalledForViewer(
-      'PhabricatorPholioApplication', $user)) {
-      $form->appendChild(
-        $this->buildMailTagCheckboxes(
-          $this->getPholioMailTags(),
-          $mailtags)
-          ->setLabel(pht('Pholio')));
+    // Sort them, then put "Common" at the top.
+    $tag_groups = isort($tag_groups, 0);
+    if ($common_tags) {
+      array_unshift($tag_groups, array(pht('Common'), $common_tags));
+    }
+
+    // Finally, build the controls.
+    foreach ($tag_groups as $spec) {
+      list($label, $map) = $spec;
+      $control = $this->buildMailTagControl($label, $map, $mailtags);
+      $form->appendChild($control);
     }
 
     $form
@@ -173,91 +180,44 @@
         ));
   }
 
-  private function getMailTags() {
-    return array(
-      MetaMTANotificationType::TYPE_DIFFERENTIAL_REVIEW_REQUEST =>
-        pht('A revision is created.'),
-      MetaMTANotificationType::TYPE_DIFFERENTIAL_UPDATED =>
-        pht('A revision is updated.'),
-      MetaMTANotificationType::TYPE_DIFFERENTIAL_COMMENT =>
-        pht('Someone comments on a revision.'),
-      MetaMTANotificationType::TYPE_DIFFERENTIAL_REVIEWERS =>
-        pht("A revision's reviewers change."),
-      MetaMTANotificationType::TYPE_DIFFERENTIAL_CLOSED =>
-        pht('A revision is closed.'),
-      MetaMTANotificationType::TYPE_DIFFERENTIAL_CC =>
-        pht("A revision's CCs change."),
-      MetaMTANotificationType::TYPE_DIFFERENTIAL_OTHER =>
-        pht('Other revision activity not listed above occurs.'),
-      MetaMTANotificationType::TYPE_MANIPHEST_STATUS =>
-        pht("A task's status changes."),
-      MetaMTANotificationType::TYPE_MANIPHEST_OWNER =>
-        pht("A task's owner changes."),
-      MetaMTANotificationType::TYPE_MANIPHEST_COMMENT =>
-        pht('Someone comments on a task.'),
-      MetaMTANotificationType::TYPE_MANIPHEST_PRIORITY =>
-        pht("A task's priority changes."),
-      MetaMTANotificationType::TYPE_MANIPHEST_CC =>
-        pht("A task's CCs change."),
-      MetaMTANotificationType::TYPE_MANIPHEST_PROJECTS =>
-        pht("A task's associated projects change."),
-      MetaMTANotificationType::TYPE_MANIPHEST_OTHER =>
-        pht('Other task activity not listed above occurs.'),
-      MetaMTANotificationType::TYPE_PHOLIO_STATUS =>
-        pht("A mock's status changes."),
-      MetaMTANotificationType::TYPE_PHOLIO_COMMENT =>
-        pht('Someone comments on a mock.'),
-      MetaMTANotificationType::TYPE_PHOLIO_UPDATED =>
-        pht('Mock images or descriptions change.'),
-      MetaMTANotificationType::TYPE_PHOLIO_OTHER =>
-        pht('Other mock activity not listed above occurs.'),
-    );
-  }
+  private function getAllEditorsWithTags(PhabricatorUser $user) {
+    $editors = id(new PhutilSymbolLoader())
+      ->setAncestorClass('PhabricatorApplicationTransactionEditor')
+      ->loadObjects();
 
-  private function getManiphestMailTags() {
-    return array_select_keys(
-      $this->getMailTags(),
-      array(
-        MetaMTANotificationType::TYPE_MANIPHEST_STATUS,
-        MetaMTANotificationType::TYPE_MANIPHEST_OWNER,
-        MetaMTANotificationType::TYPE_MANIPHEST_PRIORITY,
-        MetaMTANotificationType::TYPE_MANIPHEST_CC,
-        MetaMTANotificationType::TYPE_MANIPHEST_PROJECTS,
-        MetaMTANotificationType::TYPE_MANIPHEST_COMMENT,
-        MetaMTANotificationType::TYPE_MANIPHEST_OTHER,
-      ));
-  }
+    foreach ($editors as $key => $editor) {
+      // Remove editors which do not support mail tags.
+      if (!$editor->getMailTagsMap()) {
+        unset($editors[$key]);
+      }
 
-  private function getDifferentialMailTags() {
-    return array_select_keys(
-      $this->getMailTags(),
-      array(
-        MetaMTANotificationType::TYPE_DIFFERENTIAL_REVIEW_REQUEST,
-        MetaMTANotificationType::TYPE_DIFFERENTIAL_UPDATED,
-        MetaMTANotificationType::TYPE_DIFFERENTIAL_COMMENT,
-        MetaMTANotificationType::TYPE_DIFFERENTIAL_CLOSED,
-        MetaMTANotificationType::TYPE_DIFFERENTIAL_REVIEWERS,
-        MetaMTANotificationType::TYPE_DIFFERENTIAL_CC,
-        MetaMTANotificationType::TYPE_DIFFERENTIAL_OTHER,
-      ));
+      // Remove editors for applications which are not installed.
+      $app = $editor->getEditorApplicationClass();
+      if ($app !== null) {
+        if (!PhabricatorApplication::isClassInstalledForViewer($app, $user)) {
+          unset($editors[$key]);
+        }
+      }
+    }
+
+    return $editors;
   }
 
-  private function getPholioMailTags() {
-    return array_select_keys(
-      $this->getMailTags(),
-      array(
-        MetaMTANotificationType::TYPE_PHOLIO_STATUS,
-        MetaMTANotificationType::TYPE_PHOLIO_COMMENT,
-        MetaMTANotificationType::TYPE_PHOLIO_UPDATED,
-        MetaMTANotificationType::TYPE_PHOLIO_OTHER,
-      ));
+  private function getAllTags(PhabricatorUser $user) {
+    $tags = array();
+    foreach ($this->getAllEditorsWithTags($user) as $editor) {
+      $tags += $editor->getMailTagsMap();
+    }
+    return $tags;
   }
 
-  private function buildMailTagCheckboxes(
+  private function buildMailTagControl(
+    $control_label,
     array $tags,
     array $prefs) {
 
     $control = new AphrontFormCheckboxControl();
+    $control->setLabel($control_label);
     foreach ($tags as $key => $label) {
       $control->addCheckbox(
         'mailtags['.$key.']',
diff --git a/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php b/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php
--- a/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php
+++ b/src/applications/slowvote/editor/PhabricatorSlowvoteEditor.php
@@ -3,6 +3,14 @@
 final class PhabricatorSlowvoteEditor
   extends PhabricatorApplicationTransactionEditor {
 
+  public function getEditorApplicationClass() {
+    return 'PhabricatorSlowvoteApplication';
+  }
+
+  public function getEditorObjectsDescription() {
+    return pht('Slowvotes');
+  }
+
   public function getTransactionTypes() {
     $types = parent::getTransactionTypes();
 
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
@@ -28,6 +28,26 @@
   private $actingAsPHID;
   private $disableEmail;
 
+
+  /**
+   * Get the class name for the application this editor is a part of.
+   *
+   * Uninstalling the application will disable the editor.
+   *
+   * @return string Editor's application class name.
+   */
+  abstract public function getEditorApplicationClass();
+
+
+  /**
+   * Get a description of the objects this editor edits, like "Differential
+   * Revisions".
+   *
+   * @return string Human readable description of edited objects.
+   */
+  abstract public function getEditorObjectsDescription();
+
+
   public function setActingAsPHID($acting_as_phid) {
     $this->actingAsPHID = $acting_as_phid;
     return $this;
@@ -40,6 +60,7 @@
     return $this->getActor()->getPHID();
   }
 
+
   /**
    * When the editor tries to apply transactions that have no effect, should
    * it raise an exception (default) or drop them and continue?
@@ -1933,6 +1954,15 @@
   /**
    * @task mail
    */
+  public function getMailTagsMap() {
+    // TODO: We should move shared mail tags, like "comment", here.
+    return array();
+  }
+
+
+  /**
+   * @task mail
+   */
   protected function getMailAction(
     PhabricatorLiskDAO $object,
     array $xactions) {