4.3.0 - 5th Nov 2026 

Send queue with retry for failed deliveries 

Until now, a recipient to whom a mail could not be delivered was treated like a recipient who got the mail. There was no retry, and failed deliveries were only visible in the log of your mail server.

Failed deliveries are now written to a send queue and retried with increasing waiting times (1 minute, 5 minutes, 15 minutes, 1 hour, then every 4 hours). Permanent errors (SMTP reply code 5xx) are not retried, the recipient is marked as failed right away. After 20 attempts (configurable) a recipient is marked as failed for good.

  • The module Mail > Queue shows waiting and failed recipients with the error message, the number of attempts and the time of the next attempt. The status is shown as an icon. You can send again or delete an entry after a confirmation.
  • The email address of a delivery can be corrected before it is sent again. This function is a feature flag and disabled by default. Enable the new extension setting enableEmailCorrection (tab "feature") to use it.
  • Recipients that failed for good get an additional entry with the response type "failed" in the mail log, with the SMTP code as return code. They are counted in the mail report. If such a recipient is delivered later, the entry is removed and the delivery is counted as sent.
  • The list is refreshed automatically. The interval is set by the new page TSconfig setting undeliveredRefreshRate (5 seconds by default, 0 turns it off), which can also be changed in the configuration dialog of the queue module. Only a small request runs each cycle, the list is reloaded when it has changed. It is not replaced while an email address is being edited.
  • The correction of an address only applies to this delivery (and needs the feature flag). The new PSR-14 event RecipientEmailCorrectedEvent makes it possible to apply it to the recipient record (tt_address, fe_users and so on) as well.
  • The retry runs after each cycle of mail:mass-mailing. You can also run it more often with the new command mail:retry-failed.

See Send queue for details.

Alerting by webhook and email 

New extension settings under the tab "queue":

  • alertWebhookUrl: An HTTPS address that is called when the queue turns unhealthy, and when it is healthy again. The request is a JSON POST with a text field, so a Slack incoming webhook works as it is.
  • alertThresholdMinutes and notifyOnRecovery control when the webhook is called.
  • opsNotificationEmail: An email address that is notified for every recipient that failed for good.

Both are disabled as long as the settings are empty.

No overlapping send runs 

mail:mass-mailing and mail:retry-failed now share a lock. If a run is still active when the next one starts, the new run ends immediately. This prevents duplicate mails, for example if a scheduler task takes longer than its interval.

send-per-cycle is a limit for the whole run 

The number of mails that are sent per cycle was counted per recipient source, so a mail with several recipient groups of different sources could send more mails per cycle than configured. The limit now applies to the whole run.

Manual delivery is switched off by default 

The box with the button "Start sending manually" is only shown in the queue module, if "Send per click" in the configuration dialog of the module (sendPerCycle) is greater than 0. The dialog now accepts 0. The action behind the button refuses to run as well, if the value is 0, so it cannot be called by url either.

The default of "Send per click" changed from 50 to 0. This means, the manual delivery is switched off by default and mails are only sent by the scheduler or the command mail:mass-mailing.

Module is reloaded after saving a configuration dialog 

If you changed a value in the configuration dialog of the queue module (or of the wizard) and saved it, the module below the dialog still showed the old values until the page was reloaded manually. For example the number of mails in the text of the manual delivery stayed the same. The module is now reloaded automatically when the dialog closes after saving. Closing the dialog without saving does not reload anything.

Note instead of an empty table in the queue module 

If the mail queue is empty, the queue module now shows an info box with the text "The mail queue is empty" instead of a table with a single row. The box disappears by itself as soon as a mail is in the queue, also if another user scheduled it, or if there are failed deliveries in the retry queue. It appears again when the queue is empty. The check uses the interval refreshRate, the failed deliveries are checked with undeliveredRefreshRate.

Class attributes in the mail: keep the ones that are still needed 

The class attributes of a mail are removed after the CSS has been inlined (removeClassAttributes, enabled by default). This also removed the classes which are still needed by CSS rules that cannot be inlined, so a rule like .body a:hover never applied.

  • The new option removeClassAttributes.smart = 1 keeps exactly the classes that are still referenced by such rules and removes all others. Example:

    mail.10.removeClassAttributes = 1
    mail.10.removeClassAttributes.smart = 1
    Copied!
  • removeClassAttributes.keepOnTags keeps the class attribute of the given tags, removeClassAttributes.keepClasses keeps the given classes on all other elements. Both can be combined with smart.
  • The same is available in the Fluid template with the new arguments removeClassAttributes, keepOnTags, keepClasses and smartClassRemoval of the EmogrifyViewHelper.
  • Fix: The class was only removed from elements which had another attribute. <div class="body"> kept its class. The class attributes are now removed from every element. If your styles depended on a class that stayed by accident, add it with keepClasses.
  • New options of the CSS inliner for the content object EMOGRIFIER: addExcludedCssSelector and removeExcludedCssSelector. They require pelago/emogrifier 7.3 or higher, the extension now requires this version.
  • EmogrifierUtility::emogrify() has strict parameter types now (string, string, bool, array) and does not accept null for the HTML and the CSS anymore. This only affects your own code, which calls the method directly.

See EMOGRIFIER and SCSS.

Improved detection of returned mails 

Several users reported that returned mails (bounces) got a wrong reason and showed up in the wrong category of the report module. The detection is rewritten:

  • The type and the reason are taken from the delivery status notification (RFC 3464) of the mail server, which carries the enhanced status code (for example 5.1.1 for an unknown recipient, 5.2.2 for a full mailbox). Only returned mails without a notification are classified by the text, in a defined order (for example "User unknown" counts before "Recipient address rejected").
  • The quoted original of your mailing is not searched anymore. Before, words like "quota exceeded" in a newsletter could decide about the category of a returned mail.
  • Rejections by a spam filter or a policy (status 5.7.x, "access denied", "relay denied") are no longer reported as "Recipient unknown". They have their own category "Blocked by policy or spam filter".
  • Temporary problems (status 4.x.x, greylisting) are separated as "Temporary problems" and are not counted as permanent failures. A button "Disable returned recipients" only affects permanent bounces and no longer affects policy rejections and temporary problems.
  • Returned mails with an unknown reason are stored now. Before they could get lost, because the reason was written as -1 into an unsigned column, which failed on databases in strict mode. The message stayed in the mailbox and was read again with every run.
  • Auto replies (out of office) and delivery receipts are ignored. The text is decoded (quoted-printable, base64, charsets), several recipients in one notification are handled and a recipient is counted once per mailing.
  • The mail:analyze-bounce-mail command continues after an error in one message and prints a summary. The new options --dry-run and --unseen-only are described in AnalyzeBounceMailCommand.
  • The return code of the log is now the real SMTP code (before it was an internal code, which collided with the codes of the send queue). The report uses the new columns bounce_type, bounce_reason and dsn_status.

See Mail Report.

Mail object properties as markers 

Similar to the properties of a recipient, the properties of the mail object can be used as markers in the content of an email. For example, the reply-to address can be printed in the footer with ###MAIL_replyToEmail###.

  • All properties of the mail object with a getter named get<Property> can be used. The allowed properties are set by the new extension setting mailFields.
  • By default these markers are available: ###MAIL_subject###, ###MAIL_fromEmail###, ###MAIL_fromName###, ###MAIL_replyToEmail###, ###MAIL_replyToName### and ###MAIL_organisation###.

New commands 

  • mail:retry-failed: Delivers waiting recipients whose retry time is reached.
  • mail:queue-cleanup: Removes delivered and failed entries after the configured retention time (sentRetentionDays: 30 days, failedRetentionDays: 90 days by default).
  • mail:queue-purge: Deletes all queue entries with a given status. Without --force it only shows how many entries would be deleted.

See Command Controller.

Update notes 

Recommended after the update:

  • Set opsNotificationEmail and/or alertWebhookUrl in the extension settings.
  • Add mail:queue-cleanup as a daily scheduler task.
  • Make sure the sending is done by the scheduler (mail:mass-mailing). The manual delivery in the queue module is switched off by default now.
  • Enable enableEmailCorrection, if editors should be able to correct the email address of failed deliveries. It is disabled by default.

If a mail is deleted in the Queue or Reports module, its queue entries are deleted as well. Queue entries of a mail that was deleted another way are removed with the next send run, without a failure message.

For developers 

  • New event MEDIAESSENZ\Mail\Events\RecipientEmailCorrectedEvent, see RecipientEmailCorrectedEvent. The constructor of SendQueueService got the event dispatcher as an additional argument. The class is internal.
  • MEDIAESSENZ\Mail\Mail\MailMessage::sendOrFail() sends a message and lets transport errors through. The existing send() keeps its behavior and still returns false on errors.
  • MEDIAESSENZ\Mail\Service\MailerService::resendToRecipient() returns the sent format (MEDIAESSENZ\Mail\Type\Bitmask\SendFormat) instead of nothing. It is used by the retry and not meant to be called by other extensions.
  • New event MEDIAESSENZ\Mail\Events\RecipientBouncedEvent, see RecipientBouncedEvent. The values in the data of the DeactivateRecipientsEvent under returnCodes are the values of MEDIAESSENZ\Mail\Type\Enumeration\BounceReason now.
  • The internal class MEDIAESSENZ\Mail\Utility\BounceMailUtility is removed, the analysis is done by MEDIAESSENZ\Mail\Bounce\BounceAnalyzer. The enumeration ReturnCodes is deprecated, it was not used for real codes. LogRepository::findReturnCodesByMail() is replaced by findBounceStatsByMail().
  • The new classes SendQueueService, AlertService, SendQueueLock and the repositories for the queue are internal. Their signatures may change.
  • The extension now has unit and functional tests for the send queue (composer test:unit, composer test:functional).