From 0fc833ac3a1cf0bc631a01d7e381224c12639e0d Mon Sep 17 00:00:00 2001
From: Oliver Bartsch <bo@cedev.de>
Date: Thu, 10 Sep 2020 19:55:11 +0200
Subject: [PATCH] [FEATURE] Add FluidEmail option to EXT:form EmailFinisher

EXT:form, the last system extension not using FluidEmail
was extended for the corresponing integration. To allow
extension authors to smoothly test and upgrade, the old
StandaloneView functionality remains for now.

As there is no change to the present behaviour the switch
can be done without further actions required - if using the
default templates currently.

To migrate custom templates to FluidEmail, the template files
must be changed from the {@format}.html syntax to appropriate
names with the correct format extension like `.html` and `.txt`.
Furthermore the `templateName` option must be set with the new name.

Two fields are introduced to configure FluidEmail in the finishers:

- `useFluidEmail`: Enables sending the mails via FluidEmail
- `title`: Used for the title section of the default template

Resolves: #90728
Releases: master, 10.4
Change-Id: I378b733554ba734ad4eb6dff60e1da48ca03c972
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/65699
Tested-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Tested-by: Oliver Bartsch <bo@cedev.de>
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Georg Ringer <georg.ringer@gmail.com>
Reviewed-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Reviewed-by: Oliver Bartsch <bo@cedev.de>
Reviewed-by: Georg Ringer <georg.ringer@gmail.com>
---
 typo3/sysext/core/Classes/Mail/FluidEmail.php |  10 +
 ...FluidEmailOptionToEXTformEmailFinisher.rst |  67 +++++
 .../Domain/Finishers/EmailFinisher.php        | 238 +++++++++---------
 .../Yaml/Finishers/EmailToReceiver.yaml       |   6 +
 .../Yaml/Finishers/EmailToSender.yaml         |   6 +
 .../Configuration/Yaml/FormElements/Form.yaml |  30 +++
 .../Configuration/Yaml/Legacy/mixins.yaml     |  15 ++
 .../Yaml/NewForms/SimpleContactForm.yaml      |   2 +
 .../Templates/Finishers/Email/Default.html    |  45 ++++
 .../Templates/Finishers/Email/Default.txt     |  16 ++
 .../Resources/Private/Language/Database.xlf   |  30 +++
 11 files changed, 351 insertions(+), 114 deletions(-)
 create mode 100644 typo3/sysext/core/Documentation/Changelog/master/Feature-90728-AddFluidEmailOptionToEXTformEmailFinisher.rst
 create mode 100644 typo3/sysext/form/Resources/Private/Frontend/Templates/Finishers/Email/Default.html
 create mode 100644 typo3/sysext/form/Resources/Private/Frontend/Templates/Finishers/Email/Default.txt

diff --git a/typo3/sysext/core/Classes/Mail/FluidEmail.php b/typo3/sysext/core/Classes/Mail/FluidEmail.php
index 9c0b51bd632e..cd698b67b700 100644
--- a/typo3/sysext/core/Classes/Mail/FluidEmail.php
+++ b/typo3/sysext/core/Classes/Mail/FluidEmail.php
@@ -26,6 +26,7 @@ use TYPO3\CMS\Core\Information\Typo3Information;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Fluid\View\StandaloneView;
 use TYPO3\CMS\Fluid\View\TemplatePaths;
+use TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperVariableContainer;
 
 /**
  * Send out templated HTML/plain text emails with Fluid.
@@ -143,6 +144,15 @@ class FluidEmail extends Email
         return parent::getBody();
     }
 
+    /**
+     * @return ViewHelperVariableContainer
+     * @internal Only used for ext:form, not part of TYPO3 Core API.
+     */
+    public function getViewHelperVariableContainer(): ViewHelperVariableContainer
+    {
+        return $this->view->getRenderingContext()->getViewHelperVariableContainer();
+    }
+
     protected function generateTemplatedBody(): void
     {
         if (in_array(static::FORMAT_HTML, $this->format, true)) {
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-90728-AddFluidEmailOptionToEXTformEmailFinisher.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-90728-AddFluidEmailOptionToEXTformEmailFinisher.rst
new file mode 100644
index 000000000000..f55f862e918b
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/master/Feature-90728-AddFluidEmailOptionToEXTformEmailFinisher.rst
@@ -0,0 +1,67 @@
+.. include:: ../../Includes.txt
+
+=================================================================
+Feature: #90728 - Add FluidEmail option to EXT:form EmailFinisher
+=================================================================
+
+See :issue:`90728`
+
+Description
+===========
+
+After the introduction of FluidEmail in v10 the option to send mails in a
+standardized way is now also added to the EmailFinisher of the system extension
+EXT:from.
+
+To use FluidEmail a new option `useFluidEmail` is added to both the EmailToReceiver
+and EmailToSender finisher. It defaults to :php:`FALSE` so extension authors are
+able to smoothly test and upgrade their forms. Furthermore a new option `title`
+is available which can be used to add an E-Mail title to the default FluidEmail
+template. This option is capable of rendering form element variables using the
+known bracket syntax and can be overwritten in the FlexForm configuration of the
+form plugin.
+
+To customize the templates beeing used following options can be set:
+
+* `templateName`: The template name (for both HTML and plaintext) without the extension
+* `templateRootPaths`: The paths to the templates
+* `partialRootPaths`: The paths to the partials
+* `layoutRootPaths`: The paths to the layouts
+
+For FluidEmail, the field `templatePathAndFilename` is not evaluated anymore.
+
+A finisher configuration could look like this:
+
+.. code-block:: yaml
+
+   identifier: contact
+   type: Form
+   prototypeName: standard
+   finishers:
+   -
+      identifier: EmailToSender
+      options:
+         subject: 'Your Message: {message}'
+         title: 'Hello {name}, your confirmation'
+         templateName: ContactForm
+         templateRootPaths:
+            100: 'EXT:sitepackage/Resources/Private/Templates/Email/'
+         partialRootPaths:
+            100: 'EXT:sitepackage/Resources/Private/Partials/Email/'
+         addHtmlPart: true
+         useFluidEmail: true
+
+Please note that the old template name syntax `{@format}.html` does not work for
+FluidEmail as each format needs a different template with the corresponing file
+extension. In the example above the following files must exist in the specified
+template path:
+
+* `ContactForm.html`
+* `ContactForm.txt`
+
+Impact
+======
+
+It's now possible to use FluidEmail for sending mails in EXT:form.
+
+.. index:: Fluid, Frontend, ext:form
diff --git a/typo3/sysext/form/Classes/Domain/Finishers/EmailFinisher.php b/typo3/sysext/form/Classes/Domain/Finishers/EmailFinisher.php
index 709a7162c0e6..eeee910f488c 100644
--- a/typo3/sysext/form/Classes/Domain/Finishers/EmailFinisher.php
+++ b/typo3/sysext/form/Classes/Domain/Finishers/EmailFinisher.php
@@ -18,10 +18,13 @@ declare(strict_types=1);
 namespace TYPO3\CMS\Form\Domain\Finishers;
 
 use Symfony\Component\Mime\Address;
+use TYPO3\CMS\Core\Mail\FluidEmail;
+use TYPO3\CMS\Core\Mail\Mailer;
 use TYPO3\CMS\Core\Mail\MailMessage;
-use TYPO3\CMS\Core\Utility\MathUtility;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Extbase\Domain\Model\FileReference;
 use TYPO3\CMS\Fluid\View\StandaloneView;
+use TYPO3\CMS\Fluid\View\TemplatePaths;
 use TYPO3\CMS\Form\Domain\Finishers\Exception\FinisherException;
 use TYPO3\CMS\Form\Domain\Model\FormElements\FileUpload;
 use TYPO3\CMS\Form\Domain\Runtime\FormRuntime;
@@ -33,9 +36,11 @@ use TYPO3\CMS\Form\ViewHelpers\RenderRenderableViewHelper;
  *
  * Options:
  *
- * - templatePathAndFilename (mandatory): Template path and filename for the mail body
- * - layoutRootPath: root path for the layouts
- * - partialRootPath: root path for the partials
+ * - templatePathAndFilename (mandatory for Mail): Template path and filename for the mail body
+ * - templateName (mandatory for FluidEmail): Template name for the mail body
+ * - templateRootPaths: root paths for the templates
+ * - layoutRootPaths: root paths for the layouts
+ * - partialRootPaths: root paths for the partials
  * - variables: associative array of variables which are available inside the Fluid template
  *
  * The following options control the mail sending. In all of them, placeholders in the form
@@ -49,7 +54,6 @@ use TYPO3\CMS\Form\ViewHelpers\RenderRenderableViewHelper;
  * - replyToRecipients: Email addresses and human-readable names of the reply-to recipients
  * - carbonCopyRecipients: Email addresses and human-readable names of the copy recipients
  * - blindCarbonCopyRecipients: Email addresses and human-readable names of the blind copy recipients
- * - format: Format of the email (one of the FORMAT_* constants). By default mails are sent as HTML.
  *
  * Scope: frontend
  */
@@ -87,16 +91,19 @@ class EmailFinisher extends AbstractFinisher
         }
 
         $subject = $this->parseOption('subject');
-        $recipients = $this->getRecipients('recipients', 'recipientAddress', 'recipientName');
+        $recipients = $this->getRecipients('recipients');
         $senderAddress = $this->parseOption('senderAddress');
         $senderAddress = is_string($senderAddress) ? $senderAddress : '';
         $senderName = $this->parseOption('senderName');
         $senderName = is_string($senderName) ? $senderName : '';
-        $replyToRecipients = $this->getRecipients('replyToRecipients', 'replyToAddress');
-        $carbonCopyRecipients = $this->getRecipients('carbonCopyRecipients', 'carbonCopyAddress');
-        $blindCarbonCopyRecipients = $this->getRecipients('blindCarbonCopyRecipients', 'blindCarbonCopyAddress');
-        $addHtmlPart = $this->isHtmlPartAdded();
+        $replyToRecipients = $this->getRecipients('replyToRecipients');
+        $carbonCopyRecipients = $this->getRecipients('carbonCopyRecipients');
+        $blindCarbonCopyRecipients = $this->getRecipients('blindCarbonCopyRecipients');
+        $addHtmlPart = $this->parseOption('addHtmlPart') ? true : false;
         $attachUploads = $this->parseOption('attachUploads');
+        $useFluidEmail = $this->parseOption('useFluidEmail');
+        $title = $this->parseOption('title');
+        $title = is_string($title) && $title !== '' ? $title : $subject;
 
         if (empty($subject)) {
             throw new FinisherException('The option "subject" must be set for the EmailFinisher.', 1327060320);
@@ -108,9 +115,23 @@ class EmailFinisher extends AbstractFinisher
             throw new FinisherException('The option "senderAddress" must be set for the EmailFinisher.', 1327060210);
         }
 
-        $mail = $this->objectManager->get(MailMessage::class);
+        $formRuntime = $this->finisherContext->getFormRuntime();
 
-        $mail->from(new Address($senderAddress, $senderName))
+        $translationService = TranslationService::getInstance();
+        if (is_string($this->options['translation']['language'] ?? null) && $this->options['translation']['language'] !== '') {
+            $languageBackup = $translationService->getLanguage();
+            $translationService->setLanguage($this->options['translation']['language']);
+        }
+
+        $mail = $useFluidEmail
+            ? $this
+                ->initializeFluidEmail($formRuntime)
+                ->format($addHtmlPart ? FluidEmail::FORMAT_BOTH : FluidEmail::FORMAT_PLAIN)
+                ->assign('title', $title)
+            : GeneralUtility::makeInstance(MailMessage::class);
+
+        $mail
+            ->from(new Address($senderAddress, $senderName))
             ->to(...$recipients)
             ->subject($subject);
 
@@ -126,36 +147,30 @@ class EmailFinisher extends AbstractFinisher
             $mail->bcc(...$blindCarbonCopyRecipients);
         }
 
-        $formRuntime = $this->finisherContext->getFormRuntime();
-
-        $translationService = TranslationService::getInstance();
-        if (isset($this->options['translation']['language']) && !empty($this->options['translation']['language'])) {
-            $languageBackup = $translationService->getLanguage();
-            $translationService->setLanguage($this->options['translation']['language']);
-        }
-
-        $parts = [
-            [
-                'format' => 'Plaintext',
-                'contentType' => 'text/plain',
-            ],
-        ];
-
-        if ($addHtmlPart) {
-            $parts[] = [
-                'format' => 'Html',
-                'contentType' => 'text/html',
+        if (!$useFluidEmail) {
+            $parts = [
+                [
+                    'format' => 'Plaintext',
+                    'contentType' => 'text/plain',
+                ],
             ];
-        }
 
-        foreach ($parts as $i => $part) {
-            $standaloneView = $this->initializeStandaloneView($formRuntime, $part['format']);
-            $message = $standaloneView->render();
+            if ($addHtmlPart) {
+                $parts[] = [
+                    'format' => 'Html',
+                    'contentType' => 'text/html',
+                ];
+            }
+
+            foreach ($parts as $i => $part) {
+                $standaloneView = $this->initializeStandaloneView($formRuntime, $part['format']);
+                $message = $standaloneView->render();
 
-            if ($part['contentType'] === 'text/plain') {
-                $mail->text($message);
-            } else {
-                $mail->html($message);
+                if ($part['contentType'] === 'text/plain') {
+                    $mail->text($message);
+                } else {
+                    $mail->html($message);
+                }
             }
         }
 
@@ -163,10 +178,8 @@ class EmailFinisher extends AbstractFinisher
             $translationService->setLanguage($languageBackup);
         }
 
-        $elements = $formRuntime->getFormDefinition()->getRenderablesRecursively();
-
         if ($attachUploads) {
-            foreach ($elements as $element) {
+            foreach ($formRuntime->getFormDefinition()->getRenderablesRecursively() as $element) {
                 if (!$element instanceof FileUpload) {
                     continue;
                 }
@@ -175,13 +188,12 @@ class EmailFinisher extends AbstractFinisher
                     if ($file instanceof FileReference) {
                         $file = $file->getOriginalResource();
                     }
-
                     $mail->attach($file->getContents(), $file->getName(), $file->getMimeType());
                 }
             }
         }
 
-        $mail->send();
+        $useFluidEmail ? GeneralUtility::makeInstance(Mailer::class)->send($mail) : $mail->send();
     }
 
     /**
@@ -192,7 +204,7 @@ class EmailFinisher extends AbstractFinisher
      */
     protected function initializeStandaloneView(FormRuntime $formRuntime, string $format): StandaloneView
     {
-        $standaloneView = $this->objectManager->get(StandaloneView::class);
+        $standaloneView = GeneralUtility::makeInstance(StandaloneView::class);
 
         if (isset($this->options['templatePathAndFilename'])) {
             $this->options['templatePathAndFilename'] = strtr($this->options['templatePathAndFilename'], [
@@ -225,97 +237,95 @@ class EmailFinisher extends AbstractFinisher
             $standaloneView->setLayoutRootPaths($this->options['layoutRootPaths']);
         }
 
-        if (isset($this->options['variables'])) {
+        if (is_array($this->options['variables'] ?? null)) {
             $standaloneView->assignMultiple($this->options['variables']);
         }
 
         $standaloneView->assign('form', $formRuntime);
         $standaloneView->getRenderingContext()
+                       ->getViewHelperVariableContainer()
+                       ->addOrUpdate(RenderRenderableViewHelper::class, 'formRuntime', $formRuntime);
+
+        return $standaloneView;
+    }
+
+    protected function initializeFluidEmail(FormRuntime $formRuntime): FluidEmail
+    {
+        $templateConfiguration = $GLOBALS['TYPO3_CONF_VARS']['MAIL'];
+
+        if (is_array($this->options['templateRootPaths'] ?? null)) {
+            $templateConfiguration['templateRootPaths'] = array_replace_recursive(
+                $templateConfiguration['templateRootPaths'],
+                $this->options['templateRootPaths']
+            );
+            ksort($templateConfiguration['templateRootPaths']);
+        }
+
+        if (is_array($this->options['partialRootPaths'] ?? null)) {
+            $templateConfiguration['partialRootPaths'] = array_replace_recursive(
+                $templateConfiguration['partialRootPaths'],
+                $this->options['partialRootPaths']
+            );
+            ksort($templateConfiguration['partialRootPaths']);
+        }
+
+        if (is_array($this->options['layoutRootPaths'] ?? null)) {
+            $templateConfiguration['layoutRootPaths'] = array_replace_recursive(
+                $templateConfiguration['layoutRootPaths'],
+                $this->options['layoutRootPaths']
+            );
+            ksort($templateConfiguration['layoutRootPaths']);
+        }
+
+        $fluidEmail = GeneralUtility::makeInstance(
+            FluidEmail::class,
+            GeneralUtility::makeInstance(TemplatePaths::class, $templateConfiguration)
+        );
+
+        if (!isset($this->options['templateName']) || $this->options['templateName'] === '') {
+            throw new FinisherException('The option "templateName" must be set to use FluidEmail.', 1599834020);
+        }
+
+        // Migrate old template name to default FluidEmail name
+        if ($this->options['templateName'] === '{@format}.html') {
+            $this->options['templateName'] = 'Default';
+        }
+
+        $fluidEmail
+            ->setTemplate($this->options['templateName'])
+            ->assignMultiple([
+                'finisherVariableProvider' => $this->finisherContext->getFinisherVariableProvider(),
+                'form' => $formRuntime
+            ]);
+
+        if (is_array($this->options['variables'] ?? null)) {
+            $fluidEmail->assignMultiple($this->options['variables']);
+        }
+
+        $fluidEmail
             ->getViewHelperVariableContainer()
             ->addOrUpdate(RenderRenderableViewHelper::class, 'formRuntime', $formRuntime);
 
-        return $standaloneView;
+        return $fluidEmail;
     }
 
     /**
      * Get mail recipients
      *
      * @param string $listOption List option name
-     * @param string $singleAddressOption Single address option
-     * @param string|null $singleAddressNameOption Single address name
      * @return array
-     *
-     * @deprecated since TYPO3 v10.0, will be removed in TYPO3 v11.0.
      */
-    protected function getRecipients(
-        string $listOption,
-        string $singleAddressOption,
-        string $singleAddressNameOption = null
-    ): array {
-        $recipients = $this->parseOption($listOption);
-        $singleAddress = $this->parseOption($singleAddressOption);
-        $singleAddressName = '';
-
-        $recipients = $recipients ?? [];
-
-        if (!empty($singleAddress)) {
-            trigger_error(sprintf(
-                'EmailFinisher option "%s" is deprecated and will be removed in TYPO3 v11.0. Use "%s" instead.',
-                $singleAddressOption,
-                $listOption
-            ), E_USER_DEPRECATED);
-
-            if (!empty($singleAddressNameOption)) {
-                trigger_error(sprintf(
-                    'EmailFinisher option "%s" is deprecated and will be removed in TYPO3 v11.0. Use "%s" instead.',
-                    $singleAddressNameOption,
-                    $listOption
-                ), E_USER_DEPRECATED);
-                $singleAddressName = $this->parseOption($singleAddressNameOption);
-            }
-
-            $recipients[$singleAddress] = $singleAddressName ?: '';
-        }
-
+    protected function getRecipients(string $listOption): array
+    {
+        $recipients = $this->parseOption($listOption) ?? [];
         $addresses = [];
         foreach ($recipients as $address => $name) {
-            if (MathUtility::canBeInterpretedAsInteger($address)) {
-                $address = $name;
-                $name = '';
-            }
-            // Drop entries without mail address
-            if (empty($address)) {
+            if (!GeneralUtility::validEmail($address)) {
+                // Drop entries without valid address
                 continue;
             }
             $addresses[] = new Address($address, $name);
         }
         return $addresses;
     }
-
-    /**
-     * Get plaintext preference
-     *
-     * @return bool
-     *
-     * @deprecated since TYPO3 v10.0, will be removed in TYPO3 v11.0.
-     */
-    protected function isHtmlPartAdded(): bool
-    {
-        $format = $this->parseOption('format');
-
-        if ($format !== null) {
-            trigger_error(
-                'Usage of format option in form email finisher is deprecated - use addHtmlPart instead.',
-                E_USER_DEPRECATED
-            );
-        }
-
-        // FORMAT_HTML was the default value for "format", so
-        // FORMAT_PLAINTEXT must have been set intentionally
-        if ($format === self::FORMAT_PLAINTEXT) {
-            return false;
-        }
-
-        return $this->parseOption('addHtmlPart') ? true : false;
-    }
 }
diff --git a/typo3/sysext/form/Configuration/Yaml/Finishers/EmailToReceiver.yaml b/typo3/sysext/form/Configuration/Yaml/Finishers/EmailToReceiver.yaml
index 4c7f6181b841..158f9187767f 100644
--- a/typo3/sysext/form/Configuration/Yaml/Finishers/EmailToReceiver.yaml
+++ b/typo3/sysext/form/Configuration/Yaml/Finishers/EmailToReceiver.yaml
@@ -26,6 +26,8 @@ TYPO3:
                     attachUploads: true
                     translation:
                       language: ''
+                    useFluidEmail: false
+                    title: ''
               FormEngine:
                 label: tt_content.finishersDefinition.EmailToReceiver.label
                 elements:
@@ -149,3 +151,7 @@ TYPO3:
                           10:
                             - tt_content.finishersDefinition.EmailToReceiver.language.1
                             - default
+                  title:
+                    label: tt_content.finishersDefinition.EmailToReceiver.title.label
+                    config:
+                      type: input
diff --git a/typo3/sysext/form/Configuration/Yaml/Finishers/EmailToSender.yaml b/typo3/sysext/form/Configuration/Yaml/Finishers/EmailToSender.yaml
index 9d1ecb7ea20e..1a9281d3f4e0 100644
--- a/typo3/sysext/form/Configuration/Yaml/Finishers/EmailToSender.yaml
+++ b/typo3/sysext/form/Configuration/Yaml/Finishers/EmailToSender.yaml
@@ -24,6 +24,8 @@ TYPO3:
                     blindCarbonCopyRecipients: {  }
                     addHtmlPart: true
                     attachUploads: true
+                    useFluidEmail: false
+                    title: ''
               FormEngine:
                 label: tt_content.finishersDefinition.EmailToSender.label
                 elements:
@@ -147,3 +149,7 @@ TYPO3:
                           10:
                             - tt_content.finishersDefinition.EmailToSender.language.1
                             - default
+                  title:
+                    label: tt_content.finishersDefinition.EmailToSender.title.label
+                    config:
+                      type: input
diff --git a/typo3/sysext/form/Configuration/Yaml/FormElements/Form.yaml b/typo3/sysext/form/Configuration/Yaml/FormElements/Form.yaml
index aad73c94e650..50908f837020 100644
--- a/typo3/sysext/form/Configuration/Yaml/FormElements/Form.yaml
+++ b/typo3/sysext/form/Configuration/Yaml/FormElements/Form.yaml
@@ -193,6 +193,21 @@ TYPO3:
                             10:
                               value: default
                               label: formEditor.elements.Form.finisher.EmailToSender.editor.language.1
+                        1300:
+                          identifier: useFluidEmail
+                          templateName: Inspector-CheckboxEditor
+                          label: formEditor.elements.Form.finisher.EmailToSender.editor.useFluidEmail.label
+                          propertyPath: options.useFluidEmail
+                          fieldExplanationText: formEditor.elements.Form.finisher.EmailToSender.editor.useFluidEmail.fieldExplanationText
+                        1400:
+                          identifier: title
+                          templateName: Inspector-TextEditor
+                          label: formEditor.elements.Form.finisher.EmailToSender.editor.title.label
+                          propertyPath: options.title
+                          fieldExplanationText: formEditor.elements.Form.finisher.EmailToSender.editor.title.fieldExplanationText
+                          enableFormelementSelectionButton: true
+                          propertyValidators:
+                            10: FormElementIdentifierWithinCurlyBracesInclusive
                         9999:
                           identifier: removeButton
                           templateName: Inspector-RemoveElementEditor
@@ -319,6 +334,21 @@ TYPO3:
                             10:
                               value: default
                               label: formEditor.elements.Form.finisher.EmailToReceiver.editor.language.1
+                        1300:
+                          identifier: useFluidEmail
+                          templateName: Inspector-CheckboxEditor
+                          label: formEditor.elements.Form.finisher.EmailToReceiver.editor.useFluidEmail.label
+                          propertyPath: options.useFluidEmail
+                          fieldExplanationText: formEditor.elements.Form.finisher.EmailToReceiver.editor.useFluidEmail.fieldExplanationText
+                        1400:
+                          identifier: title
+                          templateName: Inspector-TextEditor
+                          label: formEditor.elements.Form.finisher.EmailToReceiver.editor.title.label
+                          propertyPath: options.title
+                          fieldExplanationText: formEditor.elements.Form.finisher.EmailToReceiver.editor.title.fieldExplanationText
+                          enableFormelementSelectionButton: true
+                          propertyValidators:
+                            10: FormElementIdentifierWithinCurlyBracesInclusive
                         9999:
                           identifier: removeButton
                           templateName: Inspector-RemoveElementEditor
diff --git a/typo3/sysext/form/Configuration/Yaml/Legacy/mixins.yaml b/typo3/sysext/form/Configuration/Yaml/Legacy/mixins.yaml
index 8fab3baa25a3..e74f2fceaf2c 100644
--- a/typo3/sysext/form/Configuration/Yaml/Legacy/mixins.yaml
+++ b/typo3/sysext/form/Configuration/Yaml/Legacy/mixins.yaml
@@ -963,6 +963,21 @@ TYPO3:
                   10:
                     value: default
                     label: formEditor.elements.Form.finisher.EmailToSender.editor.language.1
+              1300:
+                identifier: useFluidEmail
+                templateName: Inspector-CheckboxEditor
+                label: formEditor.elements.Form.finisher.EmailToSender.editor.useFluidEmail.label
+                propertyPath: options.useFluidEmail
+                fieldExplanationText: formEditor.elements.Form.finisher.EmailToSender.editor.useFluidEmail.fieldExplanationText
+              1400:
+                identifier: title
+                templateName: Inspector-TextEditor
+                label: formEditor.elements.Form.finisher.EmailToSender.editor.title.label
+                propertyPath: options.title
+                enableFormelementSelectionButton: true
+                propertyValidators:
+                  10: FormElementIdentifierWithinCurlyBracesInclusive
+                fieldExplanationText: formEditor.elements.Form.finisher.EmailToSender.editor.title.fieldExplanationText
               9999:
                 identifier: removeButton
                 templateName: Inspector-RemoveElementEditor
diff --git a/typo3/sysext/form/Resources/Private/Backend/Templates/FormEditor/Yaml/NewForms/SimpleContactForm.yaml b/typo3/sysext/form/Resources/Private/Backend/Templates/FormEditor/Yaml/NewForms/SimpleContactForm.yaml
index 578342030a8c..46eb3618acf7 100644
--- a/typo3/sysext/form/Resources/Private/Backend/Templates/FormEditor/Yaml/NewForms/SimpleContactForm.yaml
+++ b/typo3/sysext/form/Resources/Private/Backend/Templates/FormEditor/Yaml/NewForms/SimpleContactForm.yaml
@@ -18,6 +18,8 @@ finishers:
       attachUploads: 'true'
       translation:
         language: ''
+      useFluidEmail: 'true'
+      title: 'Confirmation of your message'
 
 renderables:
   -
diff --git a/typo3/sysext/form/Resources/Private/Frontend/Templates/Finishers/Email/Default.html b/typo3/sysext/form/Resources/Private/Frontend/Templates/Finishers/Email/Default.html
new file mode 100644
index 000000000000..08360418dda5
--- /dev/null
+++ b/typo3/sysext/form/Resources/Private/Frontend/Templates/Finishers/Email/Default.html
@@ -0,0 +1,45 @@
+<f:layout name="SystemEmail" />
+<f:section name="Title">{title}</f:section>
+<f:section name="Main">
+    <table>
+        <formvh:renderAllFormValues renderable="{form.formDefinition}" as="formValue">
+            <tr>
+                <f:if condition="{formValue.isSection}">
+                    <f:then>
+                        <td colspan="2"><b>{formvh:translateElementProperty(element: formValue.element, property: 'label')}</b></td>
+                    </f:then>
+                    <f:else>
+                        <td valign="top" align="left">{formvh:translateElementProperty(element: formValue.element, property: 'label')}</td>
+                        <td valign="top" align="left">
+                            <f:if condition="{formValue.value}">
+                                <f:then>
+                                    <f:if condition="{formValue.isMultiValue}">
+                                        <f:then>
+                                            <table cellspacing="0" border="0">
+                                                <f:for each="{formValue.processedValue}" as="value">
+                                                    <tr>
+                                                        <td>{value}</td>
+                                                    </tr>
+                                                </f:for>
+                                            </table>
+                                        </f:then>
+                                        <f:else>
+                                            <table cellspacing="0" border="0">
+                                                <tr>
+                                                    <td><f:format.nl2br>{formValue.processedValue}</f:format.nl2br></td>
+                                                </tr>
+                                            </table>
+                                        </f:else>
+                                    </f:if>
+                                </f:then>
+                                <f:else>
+                                    -
+                                </f:else>
+                            </f:if>
+                        </td>
+                    </f:else>
+                </f:if>
+            </tr>
+        </formvh:renderAllFormValues>
+    </table>
+</f:section>
diff --git a/typo3/sysext/form/Resources/Private/Frontend/Templates/Finishers/Email/Default.txt b/typo3/sysext/form/Resources/Private/Frontend/Templates/Finishers/Email/Default.txt
new file mode 100644
index 000000000000..f3109f985af2
--- /dev/null
+++ b/typo3/sysext/form/Resources/Private/Frontend/Templates/Finishers/Email/Default.txt
@@ -0,0 +1,16 @@
+<f:layout name="SystemEmail" />
+<f:section name="Title">{title}</f:section>
+<f:section name="Main">
+<formvh:renderAllFormValues renderable="{form.formDefinition}" as="formValue"><f:spaceless>
+    <f:if condition="{formValue.isMultiValue}">
+        <f:then>
+            <f:if condition="{formValue.isSection}"><f:then>*** <formvh:translateElementProperty element="{formValue.element}" property="label" /> ***</f:then><f:else><formvh:translateElementProperty element="{formValue.element}" property="label" />: <f:for each="{formValue.processedValue}" as="singleValue">- {singleValue}
+            </f:for></f:else></f:if>
+        </f:then>
+        <f:else>
+            <f:if condition="{formValue.isSection}"><f:then>*** <formvh:translateElementProperty element="{formValue.element}" property="label" /> ***</f:then><f:else><formvh:translateElementProperty element="{formValue.element}" property="label" />: <f:if condition="{formValue.processedValue}"><f:then>{formValue.processedValue -> f:format.raw()}</f:then><f:else>-</f:else></f:if></f:else></f:if>
+        </f:else>
+    </f:if>
+</f:spaceless>
+</formvh:renderAllFormValues>
+</f:section>
diff --git a/typo3/sysext/form/Resources/Private/Language/Database.xlf b/typo3/sysext/form/Resources/Private/Language/Database.xlf
index 99d2e05eea35..2ea5929f02ed 100644
--- a/typo3/sysext/form/Resources/Private/Language/Database.xlf
+++ b/typo3/sysext/form/Resources/Private/Language/Database.xlf
@@ -113,6 +113,9 @@
 			<trans-unit id="tt_content.finishersDefinition.EmailToSender.language.1" resname="tt_content.finishersDefinition.EmailToSender.language.1" xml:space="preserve">
                 <source>Default</source>
             </trans-unit>
+            <trans-unit id="tt_content.finishersDefinition.EmailToSender.title.label" resname="tt_content.finishersDefinition.EmailToSender.title.label" xml:space="preserve">
+                <source>Title</source>
+            </trans-unit>
 
             <trans-unit id="tt_content.finishersDefinition.EmailToReceiver.label" resname="tt_content.finishersDefinition.EmailToReceiver.label" xml:space="preserve">
                 <source>Email to receiver (you)</source>
@@ -147,6 +150,9 @@
             <trans-unit id="tt_content.finishersDefinition.EmailToReceiver.language.1" resname="tt_content.finishersDefinition.EmailToReceiver.language.1" xml:space="preserve">
                 <source>Default</source>
             </trans-unit>
+			<trans-unit id="tt_content.finishersDefinition.EmailToReceiver.title.label" resname="tt_content.finishersDefinition.EmailToReceiver.title.label" xml:space="preserve">
+                <source>Title</source>
+            </trans-unit>
 
             <trans-unit id="tt_content.finishersDefinition.Redirect.label" resname="tt_content.finishersDefinition.Redirect.label" xml:space="preserve">
                 <source>Redirect</source>
@@ -656,6 +662,18 @@
 			<trans-unit id="formEditor.elements.Form.finisher.EmailToSender.editor.language.1" resname="formEditor.elements.Form.finisher.EmailToSender.editor.language.1" xml:space="preserve">
                 <source>EN</source>
             </trans-unit>
+            <trans-unit id="formEditor.elements.Form.finisher.EmailToSender.editor.useFluidEmail.label" resname="formEditor.elements.Form.finisher.EmailToSender.editor.useFluidEmail.label" xml:space="preserve">
+                <source>Use FluidEmail</source>
+            </trans-unit>
+			<trans-unit id="formEditor.elements.Form.finisher.EmailToSender.editor.useFluidEmail.fieldExplanationText" resname="formEditor.elements.Form.finisher.EmailToSender.editor.useFluidEmail.fieldExplanationText" xml:space="preserve">
+                <source>If enabled, the email will be sent using the new FluidEmail rendering.</source>
+            </trans-unit>
+            <trans-unit id="formEditor.elements.Form.finisher.EmailToSender.editor.title.label" resname="formEditor.elements.Form.finisher.EmailToSender.editor.useFluidEmail.label" xml:space="preserve">
+                <source>Title</source>
+            </trans-unit>
+			<trans-unit id="formEditor.elements.Form.finisher.EmailToSender.editor.title.fieldExplanationText" resname="formEditor.elements.Form.finisher.EmailToSender.editor.useFluidEmail.fieldExplanationText" xml:space="preserve">
+                <source>Only used if FluidEmail is enabled.</source>
+            </trans-unit>
 
             <trans-unit id="formEditor.elements.Form.finisher.EmailToReceiver.editor.header.label" resname="formEditor.elements.Form.finisher.EmailToReceiver.editor.header.label" xml:space="preserve">
                 <source>Email to receiver (you)</source>
@@ -705,6 +723,18 @@
             <trans-unit id="formEditor.elements.Form.finisher.EmailToReceiver.editor.language.1" resname="formEditor.elements.Form.finisher.EmailToReceiver.editor.language.1" xml:space="preserve">
                 <source>EN</source>
             </trans-unit>
+			<trans-unit id="formEditor.elements.Form.finisher.EmailToReceiver.editor.useFluidEmail.label" resname="formEditor.elements.Form.finisher.EmailToReceiver.editor.useFluidEmail.label" xml:space="preserve">
+                <source>Use FluidEmail</source>
+            </trans-unit>
+			<trans-unit id="formEditor.elements.Form.finisher.EmailToReceiver.editor.useFluidEmail.fieldExplanationText" resname="formEditor.elements.Form.finisher.EmailToReceiver.editor.useFluidEmail.fieldExplanationText" xml:space="preserve">
+                <source>If enabled, the email will be sent using the new FluidEmail rendering.</source>
+            </trans-unit>
+			<trans-unit id="formEditor.elements.Form.finisher.EmailToReceiver.editor.title.label" resname="formEditor.elements.Form.finisher.EmailToSender.editor.useFluidEmail.label" xml:space="preserve">
+                <source>Title</source>
+            </trans-unit>
+			<trans-unit id="formEditor.elements.Form.finisher.EmailToReceiver.editor.title.fieldExplanationText" resname="formEditor.elements.Form.finisher.EmailToSender.editor.useFluidEmail.fieldExplanationText" xml:space="preserve">
+                <source>Only used if FluidEmail is enabled.</source>
+            </trans-unit>
 
             <trans-unit id="formEditor.elements.Form.finisher.Redirect.editor.header.label" resname="formEditor.elements.Form.finisher.Redirect.editor.header.label" xml:space="preserve">
                 <source>Redirect to a page</source>
-- 
GitLab