Skip to content

Email and notifications

Email

Settings

SMTP is defined under AACP → System → System Settings → Email (permission: system.settings.manage):

Setting Default
mail.enabled off (no email is sent without configuration)
mail.smtp_host / mail.smtp_port empty / 587
mail.smtp_encryption tls
mail.smtp_user / mail.smtp_password empty (the password is stored encrypted)
mail.from_email / mail.from_name empty

Next to the settings there is a button to send a test email.

Sending: with the queue

CpMailerService::sendHtml() puts the email into the sending queue; the HTTP request does not wait for the SMTP server. The real delivery is done in the background (the Messenger worker). The test email and worker handlers send immediately (sendNow).

$log = $mailer->sendHtml('user@example.com', 'Subject', '<p>Hello</p>', 'Hello');

Before using the service, check with canSend() whether the system is ready to send (if mail.enabled is off, configuration is missing).

Log and resending

Every outgoing email attempt is written to the cp_mail_logs table (recipient, subject, status, error). A failed email can be resent (resend) from the email log in AACP. Logs are cleaned after mail.log_retention_days (90 days).

Email templates

The text of the emails the system sends is not fixed in code; it is edited from the AACP → System → Email templates screen. Templates are:

  • Separate per language: each template can have a subject, an HTML body and a plain-text counterpart for each language.
  • Can be switched on and off.
  • Resettable: when a template's row is deleted, the version sent again goes back to the default that comes with the code (the translation keys stay as a fallback forever).

Emails the core sends: account verification, welcome, awaiting administrator approval, approved, identity change request and its result, and two notification envelopes.

Placeholders

ICU placeholders ({name}) and [type:property] tokens ([site:name], [user:mail]) can be used in the body. Each template definition says which placeholders the sending code passes; the screen lists them.

HTML is not sanitized

Email HTML is not site HTML: it needs tables and inline styles, which are exactly what the rich text sanitizer removes. The ability to edit templates is already at the same level as the authority to change the SMTP server (system.settings.manage), so no sanitizing is done here.

A module adds its own templates

To introduce a module's emails to AACP, MailTemplateProviderInterface is implemented; because it is tagged, the core does not have to know the module's name:

final class ShopMailTemplates implements MailTemplateProviderInterface
{
    public function mailTemplates(): array
    {
        return [new MailTemplateDefinition(key: 'shop.order_paid', labelKey: 'shop.mail.order_paid', /* default translation keys, placeholders */)];
    }
}

Notifications

A notification is the system for telling a user about an event (a comment arrived, content was approved). The single entry point is NotificationDispatcher::dispatch():

$dispatcher->dispatch('blog.comment.reply', $recipient, ['title' => $post->getTitle()], actor: $author);

Rules:

  • The in-app notification is written inside the request, because the badge needs to update immediately.
  • Email is always queued; sending a notification does not wait for SMTP.

Notification types

Each type carries an event key (eventKey), a label key and an owning module. The core introduces types; modules add their own with contributions.yaml. In their account settings the user sees which types to switch on/off.

Channels

Channel Behaviour
In-app Written to the user's notification box
Email (immediate) From the queue when the event happens
Email (digest) Collected as a daily or weekly digest

Digest emails are produced by the NotificationDigestTask task. A new channel (e.g. SMS, Slack) can be added through the channel interface (NotificationChannelInterface); the class implementing the interface is tagged in services.yaml.

User preferences

NotificationPreferenceResolver decides which channels run from the event × user preference matrix. The user can switch notification emails on and off (notif_mail_enabled) and chooses the mode: immediate, daily or weekly.

Setting Default
notification.enabled on
notification.digest.enabled on

Account area

Notification preferences are added to the account page as an account profile extension (NotificationPreferenceExtension). Other modules can also add their own sections to the account page with the same pattern (AccountProfileExtensionInterface).

Last updated: 13.10.2026