diff --git a/appengine-java21/ee8/mail/README.md b/appengine-java21/ee8/mail/README.md
new file mode 100644
index 00000000000..4c667c540d7
--- /dev/null
+++ b/appengine-java21/ee8/mail/README.md
@@ -0,0 +1,21 @@
+# JakartaMail API Email Sample for Google App Engine Standard Environment
+
+This sample demonstrates how to use [JakartaMail][jakartamail-api] on [Google App Engine
+standard environment][ae-docs].
+
+See the [sample application documentaion][sample-docs] for more detailed
+instructions.
+
+[ae-docs]: https://cloud.google.com/appengine/docs/java/
+[jakartamail-api]: https://jakartaee.github.io/mail-api/
+[sample-docs]: https://cloud.google.com/appengine/docs/java/mail/
+
+## Setup
+
+ gcloud init
+
+## Running locally
+ $ mvn appengine:run
+
+## Deploying
+ $ mvn clean package appengine:deploy
diff --git a/appengine-java21/ee8/mail/pom.xml b/appengine-java21/ee8/mail/pom.xml
new file mode 100644
index 00000000000..157e58f6157
--- /dev/null
+++ b/appengine-java21/ee8/mail/pom.xml
@@ -0,0 +1,91 @@
+
+
The Matcher for the pattern can be retrieved via getMatcherFromRequest (e.g. if groups are
+ * used in the pattern).
+ */
+ protected abstract boolean processMessage(HttpServletRequest req, HttpServletResponse res)
+ throws ServletException;
+
+ @Override
+ public void doFilter(ServletRequest sreq, ServletResponse sres, FilterChain chain)
+ throws IOException, ServletException {
+
+ HttpServletRequest req = (HttpServletRequest) sreq;
+ HttpServletResponse res = (HttpServletResponse) sres;
+
+ MimeMessage message = getMessageFromRequest(req);
+ Matcher m = applyPattern(req);
+
+ if (m != null && processMessage(req, res)) {
+ return;
+ }
+
+ chain.doFilter(req, res); // Try the next one
+ }
+
+ private Matcher applyPattern(HttpServletRequest req) {
+ Matcher m = pattern.matcher(req.getServletPath());
+ if (!m.matches()) {
+ m = null;
+ }
+
+ req.setAttribute("matcher", m);
+ return m;
+ }
+
+ protected Matcher getMatcherFromRequest(ServletRequest req) {
+ return (Matcher) req.getAttribute("matcher");
+ }
+
+ protected MimeMessage getMessageFromRequest(ServletRequest req) throws ServletException {
+ MimeMessage message = (MimeMessage) req.getAttribute("mimeMessage");
+ if (message == null) {
+ try {
+ Properties props = new Properties();
+ Session session = Session.getDefaultInstance(props, null);
+ message = new MimeMessage(session, req.getInputStream());
+ req.setAttribute("mimeMessage", message);
+
+ } catch (MessagingException e) {
+ throw new ServletException("Error processing inbound message", e);
+ } catch (IOException e) {
+ throw new ServletException("Error processing inbound message", e);
+ }
+ }
+ return message;
+ }
+}
+// [END gae_java21_mail_handler_base]
diff --git a/appengine-java21/ee8/mail/src/main/java/com/example/appengine/mail/MailHandlerServlet.java b/appengine-java21/ee8/mail/src/main/java/com/example/appengine/mail/MailHandlerServlet.java
new file mode 100644
index 00000000000..60036ed2880
--- /dev/null
+++ b/appengine-java21/ee8/mail/src/main/java/com/example/appengine/mail/MailHandlerServlet.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2016 Google LLC
+ *
+ * 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.
+ */
+
+package com.example.appengine.mail;
+
+// [START gae_java21_mail_handler_servlet]
+
+import java.io.IOException;
+import java.util.Properties;
+import java.util.logging.Logger;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.internet.MimeMessage;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class MailHandlerServlet extends HttpServlet {
+
+ private static final Logger log = Logger.getLogger(MailHandlerServlet.class.getName());
+
+ @Override
+ public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+ Properties props = new Properties();
+ Session session = Session.getDefaultInstance(props, null);
+ try {
+ MimeMessage message = new MimeMessage(session, req.getInputStream());
+ log.info("Received mail message.");
+ } catch (MessagingException e) {
+ // ...
+ }
+ // ...
+ }
+}
+// [END gae_java21_mail_handler_servlet]
diff --git a/appengine-java21/ee8/mail/src/main/java/com/example/appengine/mail/MailServlet.java b/appengine-java21/ee8/mail/src/main/java/com/example/appengine/mail/MailServlet.java
new file mode 100644
index 00000000000..ef9cb3e6370
--- /dev/null
+++ b/appengine-java21/ee8/mail/src/main/java/com/example/appengine/mail/MailServlet.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2016 Google LLC
+ *
+ * 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.
+ */
+
+package com.example.appengine.mail;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Properties;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Multipart;
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.internet.AddressException;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@SuppressWarnings("serial")
+public class MailServlet extends HttpServlet {
+
+ @Override
+ public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+ String type = req.getParameter("type");
+ if (type != null && type.equals("multipart")) {
+ resp.getWriter().print("Sending HTML email with attachment.");
+ sendMultipartMail();
+ } else {
+ resp.getWriter().print("Sending simple email.");
+ sendSimpleMail();
+ }
+ }
+
+ private void sendSimpleMail() {
+ // [START gae_java21_mail_simple]
+ Properties props = new Properties();
+ Session session = Session.getDefaultInstance(props, null);
+
+ try {
+ Message msg = new MimeMessage(session);
+ msg.setFrom(new InternetAddress("admin@example.com", "Example.com Admin"));
+ msg.addRecipient(
+ Message.RecipientType.TO, new InternetAddress("user@example.com", "Mr. User"));
+ msg.setSubject("Your Example.com account has been activated");
+ msg.setText("This is a test");
+ Transport.send(msg);
+ } catch (AddressException e) {
+ // ...
+ } catch (MessagingException e) {
+ // ...
+ } catch (UnsupportedEncodingException e) {
+ // ...
+ }
+ // [END gae_java21_mail_simple]
+ }
+
+ private void sendMultipartMail() {
+ Properties props = new Properties();
+ Session session = Session.getDefaultInstance(props, null);
+
+ String msgBody = "...";
+
+ try {
+ Message msg = new MimeMessage(session);
+ msg.setFrom(new InternetAddress("admin@example.com", "Example.com Admin"));
+ msg.addRecipient(
+ Message.RecipientType.TO, new InternetAddress("user@example.com", "Mr. User"));
+ msg.setSubject("Your Example.com account has been activated");
+ msg.setText(msgBody);
+
+ // [START gae_java21_mail_multipart]
+ String htmlBody = ""; // ...
+ byte[] attachmentData = null; // ...
+ Multipart mp = new MimeMultipart();
+
+ MimeBodyPart htmlPart = new MimeBodyPart();
+ htmlPart.setContent(htmlBody, "text/html");
+ mp.addBodyPart(htmlPart);
+
+ MimeBodyPart attachment = new MimeBodyPart();
+ InputStream attachmentDataStream = new ByteArrayInputStream(attachmentData);
+ attachment.setFileName("manual.pdf");
+ attachment.setContent(attachmentDataStream, "application/pdf");
+ mp.addBodyPart(attachment);
+
+ msg.setContent(mp);
+ // [END gae_java21_mail_multipart]
+
+ Transport.send(msg);
+
+ } catch (AddressException e) {
+ // ...
+ } catch (MessagingException e) {
+ // ...
+ } catch (UnsupportedEncodingException e) {
+ // ...
+ }
+ }
+}
diff --git a/appengine-java21/ee8/mail/src/main/webapp/WEB-INF/appengine-web.xml b/appengine-java21/ee8/mail/src/main/webapp/WEB-INF/appengine-web.xml
new file mode 100644
index 00000000000..af94612adc4
--- /dev/null
+++ b/appengine-java21/ee8/mail/src/main/webapp/WEB-INF/appengine-web.xml
@@ -0,0 +1,31 @@
+
+
+
+
+