Generate emails from entities using pluggable entity handlers

Created on 8 June 2025, 5 days ago

Problem/Motivation

Part of 🌱 [META] Adopt the symfony mailer component Needs review , proposes a way on how to integrate the upcoming email subsystem with the entity subsystem.

The observation which informed the design:

Transactional email is often bound to an entity. E.g., the password reset email is tied to a user entity, the contact email is tied to a contact message entity, and the commerce order receipt email is tied to an order entity.

Entity displays (and view modes) are used by site builders and developers to configure and tweak how content is presented when rendered in a browser, entity forms (and form modes) are used to customize forms.

What if entity "mail modes" would become the canonical way to configure how email is rendered in the recipients inbox?

Steps to reproduce

Proposed resolution

The proposed approach:

The call site (i.e., where an entity is being prepared to be sent as an email) ideally shouldn't be doing much more than specifying the "mail mode", the locale and the destination.

The entity base class provides a couple of existing methods which have similar responsibilities. E.g., EntityBase::toUrl($rel = NULL, array $options = []): Url returns a link generated from patterns defined on the entity type.

Similarly, this PR proposes EntityEmailTrait::toEmail(string $key, ?string $langcode): Email for entities which support being rendered into an email message.

With this API, the call site is quite convenient (made up example):

    $email = $entity->toEmail('subscription_start_notification', $subscriberLangcode);
    $this->mailer->send($email->from($siteEmail)->to($subscriber));

The entity subsystem provides a way to register/alter entity handlers. This mechanism can be leveraged to provide and swap out the implementation used to actually populate a message from entity values. The current PR illustrates this on the example of the User and the contact Message entities.

diff --git a/core/modules/contact/src/Entity/Message.php b/core/modules/contact/src/Entity/Message.php
index 16eae76660c..a8c90a8e47a 100644
--- a/core/modules/contact/src/Entity/Message.php
+++ b/core/modules/contact/src/Entity/Message.php
@@ -32,6 +36,13 @@
     'storage' => ContentEntityNullStorage::class,
     'view_builder' => MessageViewBuilder::class,
     'form' => ['default' => MessageForm::class],
+    'email' => [
+      'page_mail' => PageMessageBuilder::class,
+      'page_copy' => PageMessageBuilder::class,
+      'page_autoreply' => ReplyMessageBuilder::class,
+      'user_mail' => PersonalMessageBuilder::class,
+      'user_copy' => PersonalMessageBuilder::class,
+    ],
   ],
   admin_permission: 'administer contact forms',
   bundle_entity_type: 'contact_form',

The message builders registered in this PR are derived from their respective hook_mail implementations. However, in the future this architecture leaves the possibility to introduce some generic EntityMessageBuilder class configurable through the administrative interface in a similar way as EntityViewBuilder is for markup targeting the browser.

Remaining tasks

User interface changes

Introduced terminology

API changes

Data model changes

Release notes snippet

📌 Task
Status

Active

Version

11.0 🔥

Component

base system

Created by

🇨🇭Switzerland znerol

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

Production build 0.71.5 2024