Create a plug-in system to build emails with Symfony Mailer

Created on 9 August 2023, over 1 year ago
Updated 3 September 2023, over 1 year ago

Problem/Motivation

The purpose of this issue is to create the best possible framework for use by modules that want to send emails.

1) Modules that send emails typically wish to have a mailer service. These services implement an interface specific to the needs of that module. So UserMailerInterface resembles _user_mail_notify() and ContactMailerInterface is simply a rename of MailHandlerInterface.

2) The email sending code needs to be split into different parts, allowing other code including events/hooks to run in between. The sequence of steps for sending an email is like this.

  • Service switches render context, so all the following code runs in the switched context. AFAICS this was one of the main motivations for hook_mail() in the old mail API, and the argument still applies.
  • Module callback prepare() sets the langcode, to and replyTo.
  • Service switches langcode, theme, user.
  • Module callback build() fills the headers and creates the body as a render array or using a template.
  • Service renders the email body.
  • Module callback postRender() can act on the rendered body.
  • Service sends the email.
  • Module callback postSend() which for example can log the operation.

3) As part of Migration strategy for moving to Symfony Mailer Active , modules need to support converting to the old Mail interface. This would be done using a legacyMail() callback.

4) We would like to collect metadata about the mailer service, including for use by Create a system for configurable emails in Symfony Mailer Active .

  • Human-readable name for the first part (type/module) of the email ID
  • Available keys for the second part (sub-type/key) of the email ID, and their human-readable names
  • Information for the optional third part of the email ID, which is the ID of a content entity the email relates to, e.g. contact form, simplenews newsletter, web form, etc.
  • Supported token types or Twig variables (for replacement in body and perhaps subject, or other fields)
  • Configurable data required or recommended, e.g. user emails require configuring of a body and subject; contact form emails require configuring of a to address.

Proposed resolution

Answering the 4 points above:

1) Modules that send mail should implement a mailer class, with a mailer interface specific to the module. For example UserMailer implements UserMailerInterface.

2) Mailer classes should implement EmailProcessorInterface which has the required callbacks for step 2 above. This interface is also implemented by modules that alter emails.

3) Mailer classes should implement MailerInterface which extends EmailProcessorInterface with the extra callback.

4) Create plug-in @Mailer with classes implementing MailerInterface</code. Create manager <code>MailerManager implementing MailerManagerInterface.

So the module Mailer class acts both like a service and like a plug-in. We will need to figure out the details of exactly how this works. Some ideas:

  • The Mailer class is a plug-in and not a service. However it acts like a service because it implements whatever interface it prefers.
  • The Mailer class is a service. We also create dummy plug-in classes for the annotation, but instances are never created.

Sending without creating a class

The above is the recommended way for modules to send email. However for some code such as test code, prototyping, or tools, it's too complex to create a class for every different email that's sent. Instead they'd prefer simple inline code like this:

$mailer->create('module.key')->setTo($to)->setBody($body)->send();

We can't do exactly that due to point 2) from the problem/motivation section, but we can do something almost as simple. We create a FallbackMailer implementing FallbackMailerInterface. This has a single function send(array $params). Sending code will write

$fallback_mailer->send(['to' => $to, $body => $body]);
$fallback_mailer->send(['to' => $to, $build => 'my_build_function']);

The fallback mailer will process each of the parameters in the correct callback. The second case passes a callable that will be called during the build phase.

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

Feature request
Status

Active

Version

11.0 🔥

Component
Mail 

Last updated 5 days ago

No maintainer
Created by

🇬🇧United Kingdom adamps

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

Comments & Activities

Production build 0.71.5 2024