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
@@ -3294,7 +3294,7 @@
       // move the other transactions down so they provide context above the
       // actual comment.
 
-      $comment = $xaction->getBodyForMail();
+      $comment = $this->getBodyForTextMail($xaction);
       if ($comment !== null) {
         $is_comment = true;
         $comments[] = array(
@@ -3307,12 +3307,12 @@
       }
 
       if (!$is_comment || !$seen_comment) {
-        $header = $xaction->getTitleForTextMail();
+        $header = $this->getTitleForTextMail($xaction);
         if ($header !== null) {
           $headers[] = $header;
         }
 
-        $header_html = $xaction->getTitleForHTMLMail();
+        $header_html = $this->getTitleForHTMLMail($xaction);
         if ($header_html !== null) {
           $headers_html[] = $header_html;
         }
@@ -3392,12 +3392,12 @@
       // If this is not the first comment in the mail, add the header showing
       // who wrote the comment immediately above the comment.
       if (!$is_initial) {
-        $header = $xaction->getTitleForTextMail();
+        $header = $this->getTitleForTextMail($xaction);
         if ($header !== null) {
           $body->addRawPlaintextSection($header);
         }
 
-        $header_html = $xaction->getTitleForHTMLMail();
+        $header_html = $this->getTitleForHTMLMail($xaction);
         if ($header_html !== null) {
           $body->addRawHTMLSection($header_html);
         }
@@ -4983,6 +4983,58 @@
     return $xactions;
   }
 
+  private function getTitleForTextMail(
+    PhabricatorApplicationTransaction $xaction) {
+    $type = $xaction->getTransactionType();
+
+    $xtype = $this->getModularTransactionType($type);
+    if ($xtype) {
+      $xtype = clone $xtype;
+      $xtype->setStorage($xaction);
+      $comment = $xtype->getTitleForTextMail();
+      if ($comment !== false) {
+        return $comment;
+      }
+    }
+
+    return $xaction->getTitleForTextMail();
+  }
+
+  private function getBodyForHTMLMail(
+    PhabricatorApplicationTransaction $xaction) {
+    $type = $xaction->getTransactionType();
+
+    $xtype = $this->getModularTransactionType($type);
+    if ($xtype) {
+      $xtype = clone $xtype;
+      $xtype->setStorage($xaction);
+      $comment = $xtype->getTitleForHTMLMail();
+      if ($comment !== false) {
+        return $comment;
+      }
+    }
+
+    return $xaction->getTitleForHTMLMail();
+  }
+
+
+  private function getBodyForTextMail(
+    PhabricatorApplicationTransaction $xaction) {
+    $type = $xaction->getTransactionType();
+
+    $xtype = $this->getModularTransactionType($type);
+    if ($xtype) {
+      $xtype = clone $xtype;
+      $xtype->setStorage($xaction);
+      $comment = $xtype->getBodyForTextMail();
+      if ($comment !== false) {
+        return $comment;
+      }
+    }
+
+    return $xaction->getBodyForMail();
+  }
+
 
 /* -(  Extensions  )--------------------------------------------------------- */
 
diff --git a/src/applications/transactions/storage/PhabricatorModularTransactionType.php b/src/applications/transactions/storage/PhabricatorModularTransactionType.php
--- a/src/applications/transactions/storage/PhabricatorModularTransactionType.php
+++ b/src/applications/transactions/storage/PhabricatorModularTransactionType.php
@@ -431,4 +431,68 @@
     return false;
   }
 
+  // NOTE: See T12921. These APIs are somewhat aspirational. For now, all of
+  // these use "TARGET_TEXT" (even the HTML methods!) and the body methods
+  // actually return Remarkup, not text or HTML.
+
+  final public function getTitleForTextMail() {
+    return $this->getTitleForMailWithRenderingTarget(
+      PhabricatorApplicationTransaction::TARGET_TEXT);
+  }
+
+  final public function getTitleForHTMLMail() {
+    return $this->getTitleForMailWithRenderingTarget(
+      PhabricatorApplicationTransaction::TARGET_TEXT);
+  }
+
+  final public function getBodyForTextMail() {
+    return $this->getBodyForMailWithRenderingTarget(
+      PhabricatorApplicationTransaction::TARGET_TEXT);
+  }
+
+  final public function getBodyForHTMLMail() {
+    return $this->getBodyForMailWithRenderingTarget(
+      PhabricatorApplicationTransaction::TARGET_TEXT);
+  }
+
+  private function getTitleForMailWithRenderingTarget($target) {
+    $storage = $this->getStorage();
+
+    $old_target = $storage->getRenderingTarget();
+    try {
+      $storage->setRenderingTarget($target);
+      $result = $this->getTitleForMail();
+    } catch (Exception $ex) {
+      $storage->setRenderingTarget($old_target);
+      throw $ex;
+    }
+    $storage->setRenderingTarget($old_target);
+
+    return $result;
+  }
+
+  private function getBodyForMailWithRenderingTarget($target) {
+    $storage = $this->getStorage();
+
+    $old_target = $storage->getRenderingTarget();
+    try {
+      $storage->setRenderingTarget($target);
+      $result = $this->getBodyForMail();
+    } catch (Exception $ex) {
+      $storage->setRenderingTarget($old_target);
+      throw $ex;
+    }
+    $storage->setRenderingTarget($old_target);
+
+    return $result;
+  }
+
+  protected function getTitleForMail() {
+    return false;
+  }
+
+  protected function getBodyForMail() {
+    return false;
+  }
+
 }