Events
EXT:mail dispatches PSR-14 events at the places where an integrator may want to change its behavior. All events are
final classes in the namespace MEDIAESSENZ\.
| Event | Dispatched |
|---|---|
| AdditionalMailHeadersEvent | Before each single mail is sent. Adds or changes mail headers. |
| DeactivateRecipientsEvent | When returned recipients are deactivated in the report module. |
| ManipulateCsvImportDataEvent | Before the records of a csv import are written to the database. |
| ManipulateImportFolderEvent | When the folder for the uploaded csv files is determined. |
| ManipulateImportStorageFolderEvent | When the folders are collected, which can store imported records. |
| ManipulateMailRecipientsEvent | When the recipients of a mailing are determined. |
| ManipulateMarkersEvent | When the markers of a recipient are replaced in the mail content. |
| ManipulateRecipientEvent | Whenever the data of a recipient is loaded. |
| RecipientBouncedEvent | After a returned mail was analysed and stored by the bounce mail command. |
| RecipientEmailCorrectedEvent | After the address of a failed delivery was corrected in the queue module. |
| RecipientsRestrictionEvent | When the query for recipients of an extbase model source is built. |
| ScheduledSendBegunEvent | When the delivery of a mailing begins. |
| ScheduledSendFinishedEvent | When the delivery of a mailing is finished. |
Registering a listener
A listener is a class with an __ method, whose argument is the event. The simplest way to register it is
the PHP attribute As. This is also how the listeners of EXT:mail itself are registered.
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\EventListener;
use MEDIAESSENZ\Mail\Events\ManipulateMarkersEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;
#[AsEventListener(identifier: 'vendor/extensionkey/my-listener')]
final class MyListener
{
public function __invoke(ManipulateMarkersEvent $event): void
{
// change the event here
}
}
Alternatively, register the listener in the Configuration/Services.yaml of your extension:
Vendor\ExtensionKey\EventListener\MyListener:
tags:
- name: 'event.listener'
identifier: 'vendor/extensionkey/my-listener'
If several listeners change the same event, you can set their order with the arguments before and after of the
attribute (or the tag). The identifiers of the listeners of EXT:mail are the file names in kebab case with the prefix
mediaessenz/, for example mediaessenz/.
The recipient source configuration
Several events carry a MEDIAESSENZ\. It describes
the recipient source of a recipient, as it is configured in the recipient sources of your site. The most important
public properties are:
identifier- The identifier of the source, for example
tt_oraddress fe_. Csv groups have the identifierusers tx_.mail_ domain_ model_ group:<uid of the group> title- The title of the source.
tableandmodel- The database table, or the Extbase model class of the source.
ignoreMail Active - If checked, recipients are used even if they are not marked as active for mailings.
forceHtml Mail - If checked, HTML mails are sent to all recipients of the source, no matter what they chose.
custom- An array for own settings of the source.
The type of the source can be asked with the methods is, is, is,
is and is. Use them if your listener should only handle some of the sources.
AdditionalMailHeadersEvent
Dispatched for every single mail, right before it is sent. It adds the possibility to add or change mail headers.
getHeaders () - The
Symfony\of the message. Change this object. The event has no setter.Component\ Mime\ Header\ Headers getMail () - The mailing (
MEDIAESSENZ\), which the message belongs to.Mail\ Domain\ Model\ Mail getTypo3Mail Id () - The value of the header
X-. It identifies the mailing, the recipient source and the recipient and is used by the bounce analysis.TYPO3MID getandRecipient Source Identifier () getRecipient Uid () - The recipient source and the uid of the recipient, taken from the
X-. If they cannot be determined, the identifier is empty and the uid is 0. For csv and plain sources the uid is only a running number.TYPO3MID getOrganisation () - The organisation, which is sent in the header
Organization. getSite Identifier () - The identifier of the site, whose mail settings are used.
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\EventListener;
use MEDIAESSENZ\Mail\Events\AdditionalMailHeadersEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;
#[AsEventListener(identifier: 'vendor/extensionkey/add-unsubscribe-headers')]
final class AddUnsubscribeHeaders
{
public function __invoke(AdditionalMailHeadersEvent $event): void
{
if ($event->getRecipientUid() === 0) {
return;
}
// This address has to be handled by your own code. It must accept a POST request.
$url = sprintf(
'https://example.com/unsubscribe?mail=%d&source=%s&recipient=%d',
$event->getMail()->getUid(),
rawurlencode($event->getRecipientSourceIdentifier()),
$event->getRecipientUid()
);
$headers = $event->getHeaders();
$headers->addTextHeader('List-Unsubscribe', '<' . $url . '>');
$headers->addTextHeader('List-Unsubscribe-Post', 'List-Unsubscribe=One-Click');
}
}
DeactivateRecipientsEvent
Dispatched when a user deactivates the recipients of a mailing, whose mails were returned, in the report module.
EXT:mail ships listeners for the sources tt_address, fe_users and for csv and plain lists, see
MEDIAESSENZ\, MEDIAESSENZ\
and MEDIAESSENZ\. To deactivate the recipients of any other
source, it is necessary to register a listener for it.
getData () - The returned recipients, grouped by the identifier of their recipient source. For each source the array contains
recipients(for table and model sources the records of the recipients, includinguidandemail, for csv and plain sources the email addresses),return(the values ofCodes MEDIAESSENZ\, which were used to select them, empty for all) andMail\ Type\ Enumeration\ Bounce Reason configuration(the recipient source configuration). getRecipient Sources () - All recipient source configurations of the site, with their identifier as key.
getandNumber Of Affected Recipients () setNumber Of Affected Recipients () - The number of recipients, which were deactivated so far. It is shown to the user. Every listener adds the number of the recipients it deactivated.
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\EventListener;
use MEDIAESSENZ\Mail\Events\DeactivateRecipientsEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Database\ConnectionPool;
#[AsEventListener(identifier: 'vendor/extensionkey/deactivate-subscribers')]
final class DeactivateSubscribers
{
private const RECIPIENT_SOURCE = 'tx_myextension_subscriber';
public function __construct(private readonly ConnectionPool $connectionPool)
{
}
public function __invoke(DeactivateRecipientsEvent $event): void
{
$recipients = $event->getData()[self::RECIPIENT_SOURCE]['recipients'] ?? [];
$connection = $this->connectionPool->getConnectionForTable(self::RECIPIENT_SOURCE);
$affected = 0;
foreach ($recipients as $recipient) {
$affected += $connection->update(self::RECIPIENT_SOURCE, ['mail_active' => 0], ['uid' => (int)$recipient['uid']]);
}
$event->setNumberOfAffectedRecipients($event->getNumberOfAffectedRecipients() + $affected);
}
}
ManipulateCsvImportDataEvent
Dispatched during the csv import, right before the records are written to the database with the Data. It
adds the possibility to change the data of the imported records. This needs tt_address to be installed.
getandData () setData () - The data in the format of a
Datadatamap:Handler [table =>. The table is usually['NEW1' => [field => value, ...], ...]] tt_.address getConfiguration () - The configuration of the import, which consists of the page TSconfig settings of the import and the values chosen in the import wizard. It cannot be changed.
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\EventListener;
use MEDIAESSENZ\Mail\Events\ManipulateCsvImportDataEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;
#[AsEventListener(identifier: 'vendor/extensionkey/mark-imported-addresses')]
final class MarkImportedAddresses
{
public function __invoke(ManipulateCsvImportDataEvent $event): void
{
$data = $event->getData();
foreach ($data['tt_address'] ?? [] as $id => $record) {
$data['tt_address'][$id]['description'] = 'Imported on ' . date('Y-m-d');
}
$event->setData($data);
}
}
ManipulateImportFolderEvent
Dispatched when the folder for the uploaded csv files is determined. By default this is the default upload folder of
the backend user, or the first folder of one of their file mounts. The csv files are stored in a sub folder
importexport of the folder, which is set here.
getandImport Folder () setImport Folder () - The folder (
TYPO3\) orCMS\ Core\ Resource\ Folder null, if the backend user has no folder.
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\EventListener;
use MEDIAESSENZ\Mail\Events\ManipulateImportFolderEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException;
use TYPO3\CMS\Core\Resource\ResourceFactory;
#[AsEventListener(identifier: 'vendor/extensionkey/use-newsletter-import-folder')]
final class UseNewsletterImportFolder
{
public function __construct(private readonly ResourceFactory $resourceFactory)
{
}
public function __invoke(ManipulateImportFolderEvent $event): void
{
try {
// the folder "newsletter" of the storage with the uid 1, which is usually fileadmin
$event->setImportFolder($this->resourceFactory->getFolderObjectFromCombinedIdentifier('1:/newsletter/'));
} catch (FolderDoesNotExistException) {
// keep the folder of the user
}
}
}
ManipulateImportStorageFolderEvent
Dispatched when the folders are collected, which the user can choose in the csv import to store the imported records. These are the folders (pages of the type folder) the backend user is allowed to edit.
getandSub Folders () setSub Folders () - The pages of these folders as an array of records. Each record contains at least the
uidand thetitle.
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\EventListener;
use MEDIAESSENZ\Mail\Events\ManipulateImportStorageFolderEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;
#[AsEventListener(identifier: 'vendor/extensionkey/limit-import-storage-folders')]
final class LimitImportStorageFolders
{
public function __invoke(ManipulateImportStorageFolderEvent $event): void
{
$event->setSubFolders(array_values(array_filter(
$event->getSubFolders(),
static fn (array $folder): bool => str_starts_with((string)$folder['title'], 'Newsletter')
)));
}
}
ManipulateMailRecipientsEvent
Dispatched when the recipients of a mailing are determined. This happens when the mailing is finished in the mail wizard
and once more when the delivery starts, because the recipients are calculated again then. Only if the extension setting
specify is checked, the list from the wizard is kept. It adds the possibility to change the
recipients list.
getMail () - The mailing (
MEDIAESSENZ\).Mail\ Domain\ Model\ Mail getandRecipients () setRecipients () - The recipients grouped by the identifier of their recipient source. For table and model sources the list contains
the uids of the records, for csv and plain sources the recipients as arrays (
email,nameand the other fields).
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\EventListener;
use MEDIAESSENZ\Mail\Events\ManipulateMailRecipientsEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;
#[AsEventListener(identifier: 'vendor/extensionkey/remove-blocked-addresses')]
final class RemoveBlockedAddresses
{
// the uids of the addresses, which must not get any newsletter
private const BLOCKED_UIDS = [17, 42];
public function __invoke(ManipulateMailRecipientsEvent $event): void
{
$recipients = $event->getRecipients();
if (isset($recipients['tt_address'])) {
$recipients['tt_address'] = array_values(array_diff($recipients['tt_address'], self::BLOCKED_UIDS));
}
$event->setRecipients($recipients);
}
}
Tip
Usage example (filtering out recipient duplicates):
- https://gitlab.com/mediaessenz/additional-mail-recipients/-/blob/main/Classes/EventListener/FilterDuplicateRecipients.php
- https://gitlab.com/mediaessenz/additional-mail-recipients/-/blob/main/Configuration/Services.yaml
Attention: This example does not take into account that a duplicate recipient may have other categories.
ManipulateMarkersEvent
Dispatched whenever the markers (placeholders) of a recipient are replaced in the content of a mail. As the HTML and the plain text part are replaced separately, the event is dispatched twice for each recipient. It adds the possibility to add markers, for example a salutation, or to change their values.
getandMarkers () setMarkers () - The markers as an array. The keys are the markers including the hashes, for example
###USER_, the values are the texts they are replaced with.name### getRecipient () - The data of the recipient.
getRecipient Source Configuration () - The recipient source configuration of the recipient.
EXT:mail uses this event itself to add the markers in upper case, see
MEDIAESSENZ\.
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\EventListener;
use MEDIAESSENZ\Mail\Events\ManipulateMarkersEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;
#[AsEventListener(identifier: 'vendor/extensionkey/add-salutation-marker')]
final class AddSalutationMarker
{
public function __invoke(ManipulateMarkersEvent $event): void
{
if ($event->getRecipientSourceConfiguration()->identifier !== 'tt_address') {
return;
}
$markers = $event->getMarkers();
$markers['###USER_salutation###'] = match ($event->getRecipient()['gender'] ?? '') {
'f' => 'Dear Ms.',
'm' => 'Dear Mr.',
default => 'Dear',
};
$event->setMarkers($markers);
}
}
Use the new marker in the mail content like the other ones, for example ###USER_.
ManipulateRecipientEvent
Dispatched whenever the data of a recipient is loaded: for the recipient lists in the backend, right before a mail is sent to the recipient and when a tracked link is resolved. This way all of them work with the same data. It adds the possibility to change or complete the data of a recipient.
getandRecipient Data () setRecipient Data () - The data of the recipient as an array, for example
uid,emailandname. getRecipient Source Identifier () - The identifier of the recipient source, for example
tt_.address getRecipient Source Configuration () - The recipient source configuration of the recipient.
EXT:mail uses this event itself to compose the name from the first, middle and last name
(MEDIAESSENZ\) and to add the phone number of frontend users
(MEDIAESSENZ\).
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\EventListener;
use MEDIAESSENZ\Mail\Events\ManipulateRecipientEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;
#[AsEventListener(
identifier: 'vendor/extensionkey/name-fallback',
after: 'mediaessenz/mail-normalize-recipient-data'
)]
final class NameFallback
{
public function __invoke(ManipulateRecipientEvent $event): void
{
if ($event->getRecipientSourceIdentifier() !== 'tt_address') {
return;
}
$recipientData = $event->getRecipientData();
if (trim((string)($recipientData['name'] ?? '')) === '') {
$recipientData['name'] = 'customer';
$event->setRecipientData($recipientData);
}
}
}
RecipientBouncedEvent
Is dispatched by the command mail:analyze-bounce-mail after a returned mail was analysed and stored in the log of the mailing. It is not dispatched for auto replies, which are ignored, or for messages that cannot be assigned to a mailing.
EXT:mail does not change the recipient record (tt_address, fe_users, a csv list and so on). Use this event, if a recipient should be deactivated right after a permanent problem, without waiting for the manual action in the report module.
The event provides:
getType () Bouncefor a permanent problem (status 5.x.x),Type:: HARD Bouncefor a temporary one (status 4.x.x, for example greylisting or a mailbox that is temporarily full).Type:: SOFT getReason () - A
Bounce:Reason RECIPIENT_,UNKNOWN MAILBOX_,FULL HOST_,UNREACHABLE POLICY_(rejected as spam or by a policy, the recipient itself is fine),BLOCKED MESSAGE_orERROR UNKNOWN. getandSmtp Code () getDsn Status () - The SMTP reply code (e.g.
550) and the enhanced status code (e.g.5.), which the receiving server reported. They are1. 1 0or empty, if the returned mail contains none. getReason Text () - The explanation of the receiving server.
get,Recipient Source Identifier () get,Recipient Uid () getandEmail () getMail Uid () - The recipient, to whom the mail was sent, and the mailing. For sources without records, like a csv or plain list, the uid is only a running number of the list. A listener should ignore such sources.
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\EventListener;
use MEDIAESSENZ\Mail\Events\RecipientBouncedEvent;
use MEDIAESSENZ\Mail\Type\Enumeration\BounceReason;
use MEDIAESSENZ\Mail\Type\Enumeration\BounceType;
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\DataHandling\DataHandler;
use TYPO3\CMS\Core\Utility\GeneralUtility;
#[AsEventListener(identifier: 'vendor/extensionkey/deactivate-bounced-address')]
final class DeactivateBouncedAddress
{
public function __invoke(RecipientBouncedEvent $event): void
{
if ($event->getRecipientSourceIdentifier() !== 'tt_address'
|| $event->getType() !== BounceType::HARD
|| $event->getReason() !== BounceReason::RECIPIENT_UNKNOWN
) {
return;
}
$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
$dataHandler->start(['tt_address' => [$event->getRecipientUid() => ['hidden' => 1]]], []);
$dataHandler->process_datamap();
}
}
Note
Only deactivate recipients after a hard bounce with the reason RECIPIENT_. A full mailbox, a rejection
by a spam filter or a temporary problem says nothing about the address itself.
RecipientEmailCorrectedEvent
Is dispatched after the email address of a failed or waiting delivery was corrected in the queue module. The correction has to be enabled with the extension setting enableEmailCorrection, which is off by default. Without it, no address can be changed and the event is never dispatched.
The correction only applies to this delivery. EXT:mail does not change the recipient record (tt_address, fe_users, a csv list and so on). Use this event, if the corrected address should be applied to the recipient record as well.
The event is only dispatched, if the address was really changed. It is dispatched after the queue entry was saved, so an exception in a listener cannot undo the correction.
The event provides:
getRecipient Source Identifier () - The identifier of the recipient source, e.g.
tt_oraddress fe_, like in the recipient sources configuration of your site.users getRecipient Uid () - The uid of the record in this source. For sources without records, like a csv or plain list, this is only a running number of the list. A listener should ignore such sources.
getandOld Email () getNew Email () - The address before and after the correction.
get,Mail Uid () getandQueue Item Uid () getRecipient Data () - The mail, the queue entry and the recipient data of the queue entry, which already contains the new address.
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\EventListener;
use MEDIAESSENZ\Mail\Events\RecipientEmailCorrectedEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\DataHandling\DataHandler;
use TYPO3\CMS\Core\Utility\GeneralUtility;
#[AsEventListener(identifier: 'vendor/extensionkey/apply-corrected-email-to-address')]
final class ApplyCorrectedEmailToAddress
{
public function __invoke(RecipientEmailCorrectedEvent $event): void
{
if ($event->getRecipientSourceIdentifier() !== 'tt_address') {
return;
}
$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
$dataHandler->start(
['tt_address' => [$event->getRecipientUid() => ['email' => $event->getNewEmail()]]],
[]
);
$dataHandler->process_datamap();
}
}
Note
The example uses the Data. This way the change is done with the permissions of the backend user who
corrected the address, it is logged in the record history, and caches are cleared. How a listener is registered is
described above.
RecipientsRestrictionEvent
Dispatched when the query for the recipients of an Extbase model source is built. It adds the possibility to restrict the recipients with further constraints. EXT:mail has already added the constraints for active recipients, a not empty email address and the selected categories.
getRecipient Source Configuration () - The recipient source configuration of the source.
getQuery () - The
TYPO3\, which is used to create new constraints.CMS\ Extbase\ Persistence\ Query Interface getandConstraints () setConstraints () - The constraints, which are combined with a logical and. Add your constraints to this list.
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\EventListener;
use MEDIAESSENZ\Mail\Events\RecipientsRestrictionEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;
#[AsEventListener(identifier: 'vendor/extensionkey/exclude-domain')]
final class ExcludeDomain
{
public function __invoke(RecipientsRestrictionEvent $event): void
{
if ($event->getRecipientSourceConfiguration()->identifier !== 'tt_address') {
return;
}
$query = $event->getQuery();
$constraints = $event->getConstraints();
$constraints[] = $query->logicalNot($query->like('email', '%@example.invalid'));
$event->setConstraints($constraints);
}
}
ScheduledSendBegunEvent
Dispatched once for each mailing, when its delivery begins. This is before the start is stored in the mailing and before the recipients are calculated, so the number of recipients is not final yet.
getandMail () setMail () - The mailing (
MEDIAESSENZ\).Mail\ Domain\ Model\ Mail
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\EventListener;
use MEDIAESSENZ\Mail\Events\ScheduledSendBegunEvent;
use Psr\Log\LoggerInterface;
use TYPO3\CMS\Core\Attribute\AsEventListener;
#[AsEventListener(identifier: 'vendor/extensionkey/log-send-begun')]
final class LogSendBegun
{
public function __construct(private readonly LoggerInterface $logger)
{
}
public function __invoke(ScheduledSendBegunEvent $event): void
{
$mail = $event->getMail();
$this->logger->info('Mailing {uid} "{subject}" begins to send', [
'uid' => $mail->getUid(),
'subject' => $mail->getSubject(),
]);
}
}
ScheduledSendFinishedEvent
Dispatched once for each mailing, when its delivery is finished, either because all recipients were handled or because the mailing has no recipients. This is before the end is stored in the mailing.
getandMail () setMail () - The mailing (
MEDIAESSENZ\).Mail\ Domain\ Model\ Mail
<?php
declare(strict_types=1);
namespace Vendor\ExtensionKey\EventListener;
use MEDIAESSENZ\Mail\Events\ScheduledSendFinishedEvent;
use Psr\Log\LoggerInterface;
use TYPO3\CMS\Core\Attribute\AsEventListener;
#[AsEventListener(identifier: 'vendor/extensionkey/log-send-finished')]
final class LogSendFinished
{
public function __construct(private readonly LoggerInterface $logger)
{
}
public function __invoke(ScheduledSendFinishedEvent $event): void
{
$mail = $event->getMail();
$this->logger->info('Mailing {uid} "{subject}" finished: {handled} of {total} recipients handled', [
'uid' => $mail->getUid(),
'subject' => $mail->getSubject(),
'handled' => $mail->getNumberOfRecipientsHandled(),
'total' => $mail->getNumberOfRecipients(),
]);
}
}
Need more?
Please contact me