Feature: #91724 - Introduce TemplatedEmailFactory for centralized email creation
See forge#91724
Description
A new
Templated class has been
introduced that provides centralized creation of
Fluid instances.
The factory provides three methods for different use cases:
create() - For backend/CLI contexts (login notifications, scheduler tasks, install
tool) where only global configuration
$GLOBALSis used.['TYPO3_ CONF_ VARS'] ['MAIL'] [...] createFrom Request () - For frontend contexts (form submissions, felogin) where site-specific
email templates should be applied. Merges site settings
typo3/with 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, taking an optional request context into evaluation. Two cases of template resolution are then possible (ordered by priority):
- Request without site attribute: 1. Provided override arguments -> 2. global config
- Request with site attribute: 1. Provided override arguments -> 2. Site settings -> 3. global config
Note you can also utilize the numerical priority of template paths so that site settings with a higher priority number could "win" the order battle of a lower-number in the override argument paths.
Site settings
A new site set
typo3/ is available in EXT:core that defines the
following settings. 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 - Array of paths to email templates. These are merged with the global mail template paths.
email.layout Root Paths - Array of paths to email layouts. These are merged with the global mail layout paths.
email.partial Root Paths - 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 will
add entries as sequential array (numbered 0, 1, 2, ...). In this case,
all paths will just be appended to the array, giving all entries here
the highest priority.
When editing settings. manually, specific numerical array keys
can be assigned, if needed.
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\TemplatedEmailFactory;
use TYPO3\CMS\Core\Mail\MailerInterface;
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/CLI usage
For backend contexts where no site-specific templates are needed, use
create:
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Mail\TemplatedEmailFactory;
use TYPO3\CMS\Core\Mail\MailerInterface;
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 global
configuration, use
create:
use TYPO3\CMS\Core\Mail\TemplatedEmailFactory;
use TYPO3\CMS\Core\Mail\MailerInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Scheduler\Task\AbstractTask;
final class MySchedulerTask extends AbstractTask
{
public function sendReport(): void
{
// This example showcases how to use this when
// constructor-based Dependency Injection is blocked, like in the case of
// AbstractTask (EXT:scheduler). Always use DI, where possible.
$mailer = GeneralUtility::makeInstance(MailerInterface::class);
$templatedEmailFactory = GeneralUtility::makeInstance(TemplatedEmailFactory::class);
// Merge extension-specific paths with global configuration.
// Note, if you do NOT pass a `$request` argument here, no site context will be evaluated.
// You may want to check for `$GLOBALS['TYPO3_REQUEST']` in case you want this fallback,
// or a custom created 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
Remember, you can use the
Before
to do adjustments to a Fluid object, before it is sent. There you can also still assign
fluid variables or adjust parts of the email (like setting From or the mail's subject
prefix, depending on your site):
<?php
declare(strict_types=1);
use TYPO3\CMS\Core\Mail\Event\BeforeMailerSentMessageEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;
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 the
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/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
directly instantiating them. When a request with a site attribute is passed,
template paths and format from the
typo3/ site set are applied
automatically. The merge priority is (highest priority wins):
- Global
$GLOBALSpaths (base)['TYPO3_ CONF_ VARS'] ['MAIL'] - Site settings from
typo3/, when a site-based request is available, and site settings are applied.email - Caller-provided override paths, when using
create.With Overrides ()