Feature: #91724 - Introduce TemplatedEmailFactory for centralized email creation
See forge#91724
Description
A new
Templated class has been
introduced to provide a centralized creation of
Fluid instances.
The factory provides three methods for different use cases:
create() - For backend and CLI contexts, such as login notifications, Scheduler
tasks, and the Install Tool, where only the global configuration
$GLOBALSis used.['TYPO3_ CONF_ VARS'] ['MAIL'] [...] createFrom Request () - For frontend contexts, such as form submissions and
EXT:, where site-specific email templates should be applied. It merges site settings fromfelogin typo3/with the global configurationemail $GLOBALS.['TYPO3_ CONF_ VARS'] ['MAIL'] [...] createWith Overrides () -
For extensions that need to provide custom template paths merged on top of the base configuration, optionally taking a request context into account. Two cases of template resolution are possible, ordered by priority:
- Request without site attribute: 1. Provided override arguments -> 2. global configuration
- Request with site attribute: 1. Provided override arguments -> 2. site settings -> 3. global configuration
Note that you can also use the numerical priority of template paths so that site settings with a higher priority number can override paths in the provided arguments with a lower priority number.
Site settings
A new site set,
typo3/, is available in EXT:core and defines the
settings below. These are applied automatically when a request with a site
attribute is passed to
create or
create. This means extensions running in a frontend
context, such as EXT:form email finishers, benefit from site-specific email
configuration:
email.format - The email format to use (
html,plain,both). If empty, the global configuration is used. email.template Root Paths - An array of paths to email templates. These are merged with the global mail template paths.
email.layout Root Paths - An array of paths to email layouts. These are merged with the global mail layout paths.
email.partial Root Paths - An array of paths to email partials. These are merged with the global mail partial paths.
Hint
Please note that entering these paths via the site settings GUI adds
entries as a sequential array, numbered 0, 1, 2, and so on. In this case,
all paths are appended to the array, giving all entries the highest
priority.
When editing settings. manually, specific numerical array keys can
be assigned.
Usage
Frontend usage (site-aware)
For frontend contexts where site-specific templates are desired, use
create. Include the
typo3/ site set in your
site configuration:
dependencies:
- typo3/email
settings:
email:
templateRootPaths:
100: 'EXT:my_sitepackage/Resources/Private/Templates/Email/'
layoutRootPaths:
100: 'EXT:my_sitepackage/Resources/Private/Layouts/Email/'
format: 'html'
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Mail\MailerInterface;
use TYPO3\CMS\Core\Mail\TemplatedEmailFactory;
final class MyFrontendEmailService
{
public function __construct(
private readonly TemplatedEmailFactory $templatedEmailFactory,
private readonly MailerInterface $mailer,
) {}
public function sendEmail(ServerRequestInterface $request): void
{
// Uses site-specific template paths if configured
$email = $this->templatedEmailFactory->createFromRequest($request);
$email
->setTemplate('MyTemplate')
->to('recipient@example.com')
->from('sender@example.com')
->subject('My Subject')
->assign('name', 'World');
$this->mailer->send($email);
}
}
Backend and CLI usage
For backend contexts where no site-specific templates are needed, use
create:
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Mail\MailerInterface;
use TYPO3\CMS\Core\Mail\TemplatedEmailFactory;
final class MyBackendEmailService
{
public function __construct(
private readonly TemplatedEmailFactory $templatedEmailFactory,
private readonly MailerInterface $mailer,
) {}
public function sendNotification(
?ServerRequestInterface $request = null,
): void {
// Uses only global $GLOBALS['TYPO3_CONF_VARS']['MAIL'] configuration
$email = $this->templatedEmailFactory->create($request);
$email
->setTemplate('SystemNotification')
->to('admin@example.com')
->from('system@example.com')
->subject('System Notification');
$this->mailer->send($email);
}
}
Custom template path overrides
For extensions that need their own email templates merged with the global
configuration, use
create:
use TYPO3\CMS\Core\Mail\MailerInterface;
use TYPO3\CMS\Core\Mail\TemplatedEmailFactory;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Scheduler\Task\AbstractTask;
final class MySchedulerTask extends AbstractTask
{
public function sendReport(): void
{
// This example shows how to use this when constructor-based
// dependency injection is not possible, as in AbstractTask
// (EXT:scheduler). Always use dependency injection where possible.
$mailer = GeneralUtility::makeInstance(MailerInterface::class);
$templatedEmailFactory = GeneralUtility::makeInstance(
TemplatedEmailFactory::class
);
// Merge extension-specific paths with the global configuration.
// Note that if you do not pass a `$request` argument here, no site
// context is evaluated. You may want to check
// `$GLOBALS['TYPO3_REQUEST']` if you need this fallback, or use a
// custom request object.
$email = $templatedEmailFactory->createWithOverrides(
templateRootPaths: [
20 => 'EXT:my_extension/Resources/Private/Templates/Email/',
],
layoutRootPaths: [
20 => 'EXT:my_extension/Resources/Private/Layouts/',
],
);
$email
->setTemplate('Report')
->to('admin@example.com')
->from('system@example.com')
->subject('Scheduled Report');
$mailer->send($email);
}
}
Hint
You can use the
Before
to change a Fluid object before it is sent. You can
assign Fluid variables or modify parts of the email, such as setting
From or the email subject prefix, depending on your site:
<?php
declare(strict_types=1);
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Mail\Event\BeforeMailerSentMessageEvent;
final class MyMailerListener
{
#[AsEventListener('my-extension/mymailerlistener')]
public function __invoke(BeforeMailerSentMessageEvent $event): void
{
$message = $event->getMessage();
$message->from('customized@example.com');
$message->assign('mySpecialVariable', 'mySpecialContent');
$event->setMessage($message);
}
}
Core migrations
All core extensions that send emails have been migrated to use
Templated. This includes:
- EXT:form Email finishers now use
createwith the request from the form runtime, so site-specific email settings are applied automatically.With Overrides () - EXT:felogin Password recovery emails now use
create, making them site-aware. The methodWith Overrides () Recoveryhas been removed, as template path resolution is now handled by the factory.Configuration:: get Mail Template Paths () - EXT:backend Login notifications, failed login and MFA attempt
notifications, and password reset emails use
create.() - EXT:install Test email sending uses
create.() - EXT:workspaces Stage change notifications use
create.With Overrides () - EXT:linkvalidator Broken link report emails use
create.With Overrides () - EXT:reports System status emails use
create.()
Impact
Extensions that send emails are encouraged to use the
Templated to create
Fluid instances instead of
instantiating them directly. When a request with a site attribute is passed,
template paths and format from the
typo3/ site set are applied.
The merge priority, with the highest priority winning, is:
- Global
$GLOBALSpaths as the base['TYPO3_ CONF_ VARS'] ['MAIL'] - Site settings from
typo3/, when a site-based request is available and site settings are appliedemail - Caller-provided override paths when using
createWith Overrides ()