Merge "Change OutgoingEmail to use a soy instead of string concatenation."
diff --git a/java/com/google/gerrit/pgm/init/SitePathInitializer.java b/java/com/google/gerrit/pgm/init/SitePathInitializer.java
index a057e66..d0d03b5 100644
--- a/java/com/google/gerrit/pgm/init/SitePathInitializer.java
+++ b/java/com/google/gerrit/pgm/init/SitePathInitializer.java
@@ -122,6 +122,8 @@
     extractMailExample("DeleteReviewerHtml.soy");
     extractMailExample("DeleteVote.soy");
     extractMailExample("DeleteVoteHtml.soy");
+    extractMailExample("Email.soy");
+    extractMailExample("EmailHtml.soy");
     extractMailExample("Footer.soy");
     extractMailExample("FooterHtml.soy");
     extractMailExample("ChangeHeader.soy");
diff --git a/java/com/google/gerrit/server/mail/send/MailSoySauceLoader.java b/java/com/google/gerrit/server/mail/send/MailSoySauceLoader.java
index 0eaafb8..7bc319f 100644
--- a/java/com/google/gerrit/server/mail/send/MailSoySauceLoader.java
+++ b/java/com/google/gerrit/server/mail/send/MailSoySauceLoader.java
@@ -65,6 +65,8 @@
     "DeleteReviewerHtml.soy",
     "DeleteVote.soy",
     "DeleteVoteHtml.soy",
+    "Email.soy",
+    "EmailHtml.soy",
     "InboundEmailRejection.soy",
     "InboundEmailRejectionHtml.soy",
     "Footer.soy",
diff --git a/java/com/google/gerrit/server/mail/send/OutgoingEmail.java b/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
index b18b137..ca19150 100644
--- a/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
+++ b/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
@@ -41,6 +41,8 @@
 import com.google.gerrit.server.update.RetryableAction.ActionType;
 import com.google.gerrit.server.validators.OutgoingEmailValidationListener;
 import com.google.gerrit.server.validators.ValidationException;
+import com.google.template.soy.data.SanitizedContent.ContentKind;
+import com.google.template.soy.data.UnsafeSanitizedContentOrdainer;
 import com.google.template.soy.jbcsrc.api.SoySauce;
 import java.net.MalformedURLException;
 import java.net.URL;
@@ -154,6 +156,22 @@
     this.messageId = messageId;
   }
 
+  private String constructTextEmail() {
+    soyContext.put("body", textBody.toString());
+    soyContext.put("footer", textTemplate("Footer"));
+    return textTemplate("Email");
+  }
+
+  private String constructHtmlEmail() {
+    soyContext.put(
+        "body", UnsafeSanitizedContentOrdainer.ordainAsSafe(htmlBody.toString(), ContentKind.HTML));
+    soyContext.put(
+        "footer",
+        UnsafeSanitizedContentOrdainer.ordainAsSafe(
+            soyHtmlTemplate("FooterHtml"), ContentKind.HTML));
+    return soyHtmlTemplate("EmailHtml");
+  }
+
   /** Format and enqueue the message for delivery. */
   public void send() throws EmailException {
     try {
@@ -192,10 +210,6 @@
     if (messageId == null) {
       throw new IllegalStateException("All emails must have a messageId");
     }
-    appendText(textTemplate("Footer"));
-    if (useHtml()) {
-      appendHtml(soyHtmlTemplate("FooterHtml"));
-    }
 
     Set<Address> smtpRcptToPlaintextOnly = new HashSet<>();
     if (shouldSendMessage()) {
@@ -295,16 +309,15 @@
         setHeader(FieldName.REPLY_TO, j.toString());
       }
 
-      String textPart = textBody.toString();
       OutgoingEmailValidationListener.Args va = new OutgoingEmailValidationListener.Args();
       va.messageClass = messageClass;
       va.smtpFromAddress = smtpFromAddress;
       va.smtpRcptTo = smtpRcptTo;
       va.headers = headers;
-      va.body = textPart;
+      va.body = constructTextEmail();
 
       if (useHtml()) {
-        va.htmlBody = htmlBody.toString();
+        va.htmlBody = constructHtmlEmail();
       } else {
         va.htmlBody = null;
       }
diff --git a/resources/com/google/gerrit/server/mail/Email.soy b/resources/com/google/gerrit/server/mail/Email.soy
new file mode 100644
index 0000000..9afea72
--- /dev/null
+++ b/resources/com/google/gerrit/server/mail/Email.soy
@@ -0,0 +1,27 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+{namespace com.google.gerrit.server.mail.template.Email}
+
+/**
+ * The .Email template defines the structure of the content in the email.
+ */
+{template Email kind="text"}
+  {@param body: string}
+  {@param footer: string}
+  {$body}
+  {$footer}
+{/template}
diff --git a/resources/com/google/gerrit/server/mail/EmailHtml.soy b/resources/com/google/gerrit/server/mail/EmailHtml.soy
new file mode 100644
index 0000000..5b5ea63
--- /dev/null
+++ b/resources/com/google/gerrit/server/mail/EmailHtml.soy
@@ -0,0 +1,38 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+{namespace com.google.gerrit.server.mail.template.EmailHtml}
+
+/**
+ * The .EmailHtml template defines the structure of the content in the email.
+ */
+{template EmailHtml}
+  {@param styles: css}
+  {@param body: html}
+  {@param footer: html}
+  <!DOCTYPE html>
+  <html>
+    <head>
+      <style>
+        {$styles}
+      </style>
+    </head>
+    <body>
+      {$body}
+      {$footer}
+    </body>
+  </html>
+{/template}