An experimental mailer module has been added which puts the necessary services in place such that bleeding-edge contrib and custom code can start using the mail delivery part of Symfony Mailer by simply retrieving / referencing a configured mailer service from / in the container.
The new mail API is subject to breaking changes. This change record will be updated with every iteration.
Setup
Enable the experimental mailer module. Then configure the transport DSN by specifying its components in the mailer_dsn config. The following examples may serve as a starting point:
- For the default sendmail transport:
$config['system.mail']['mailer_dsn'] = [ 'scheme' => 'sendmail', 'host' => 'default', ]; - For mailpit on localhost:
$config['system.mail']['mailer_dsn'] = [ 'scheme' => 'smtp', 'host' => 'localhost', 'port' => 1025, ]; - For authenticated SMTP:
$config['system.mail']['mailer_dsn'] = [ 'scheme' => 'smtp', 'host' => 'smtp.example.com', 'user' => 'some-username@example.com', 'password' => 'correct horse battery staple', 'options' => [ 'local_domain' => 'example.org', ], ];
Preliminary Mail Delivery API
See issue #3379794: Add symfony mailer transports to Dependency Injection Container (mail delivery layer).
- Service:
Symfony\Component\Mailer\MailerInterface:
Custom and contrib modules may use this service to pass mails to the mail delivery layer. This is the main entry point for the mail delivery layer. - Service:
Symfony\Component\Mailer\Transport\TransportInterface:
Custom and contrib modules may use this service to directly inject mails to the configured mail transport for delivery. This will skip Symfony messenger (if configured). This should only be necessary in advanced use cases. - Service:
Drupal\Core\Mailer\TransportServiceFactoryInterface:
Custom and contrib modules may decorate or replace this service in order to customize construction ofSymfony\Component\Mailer\Transport\TransportInterface. This should only be necessary in advanced use cases. E.g., if certain messages are sent via dedicated transports. - Events MessageEvent, SentMessageEvent and FailedMessageEvent:
Custom and contrib modules may register event subscribers to act on emails before and after they are sent. - Abstract service
Symfony\Component\Mailer\Transport\AbstractTransportFactoryand service tagmailer.transport_factory:
Custom and contrib modules may supply third-party or custom transport factories usingSymfony\Component\Mailer\Transport\AbstractTransportFactoryas their parent service, tagged withmailer.transport_factoryand typically withSymfony\Component\Mailer\Transport\AbstractTransportFactoryas their parent class. - The
mailer_sendmail_commandssetting:
An array of command lines which are allowed as thecommandoption in the sendmail transport.
Example: Send a Message
The following example can serve as a starting point for experiments with the Symfony Mailer mail delivery API:
// Get the mailer instance from the container.
$mailer = $container->get(\Symfony\Component\Mailer\MailerInterface::class);
// Create a new message.
$email = new \Symfony\Component\Mime\Email();
$email->subject("Test message")
->from('test@localhost.localdomain')
->text('Hello test runner!');
try {
$mailer->send($email->to('admin@localhost.localdomain'));
// $messenger->addStatus($this->t('Sent test message'));
}
catch (RuntimeException $e) {
// $messenger->addError($this->t('Failed to send test message'));
}
Example: Register a Third-Party Transport
Contrib and custom modules providing third-party transports supply their own service tagged with the mailer.transport_factory tag, deriving from the abstract transport class:
services:
# Example of third-party service integration, requires symfony/google-mailer
# https://packagist.org/packages/symfony/google-mailer
Symfony\Component\Mailer\Bridge\Google\Transport\GmailTransportFactory:
parent: Symfony\Component\Mailer\Transport\AbstractTransportFactory
tags:
- { name: mailer.transport_factory }
Core Event Subscribers
OriginatorSubscriber
The Drupal\Core\Mailer\EventSubscriber\OriginatorSubscriber acts on the MessageEvent with a very low priority of -255. It ensures that the From and Sender headers are set correctly according to RFC 5322 section 3.6.2. Especially:
- A
Fromheader is present. Defaults to thesystem.site.mailwithsystem.site.nameas the display name. - A
Senderheader is present if-and-only-if necessary.
It doesn't change the From and/or the Sender header if it is already present on the message. It only removes the Sender if it is redundant according to the rules described in the RFC.
Preliminary Mail Building API
Not designed/implemented yet.
Preliminary Mail Capturing while Testing
See issue #3397420: Add a way to capture mails sent through the mailer transport service during tests.
Mails sent through the Symfony\Component\Mailer\MailerInterface and Symfony\Component\Mailer\Transport\TransportInterface services are discarded during tests. Enable the mailer_capture module in order to capture them form within a test case.
Mails captured by the mailer_capture module can be retrieved using the getMails() method from the Drupal\Core\Test\MailerCaptureTrait trait.
Example: Capture emails during test
class MailFormTest extends BrowserTestBase {
use MailerCaptureTrait;
protected static $modules = ['mailer', 'mailer_capture'];
public function testMailForm(): void {
// Before we send the email, getEmails should return an empty array.
$capturedEmails = $this->getEmails();
$this->assertCount(0, $capturedEmails, 'The captured emails queue is empty.');
$this->drupalGet('/mailer-capture-test/send-mail');
$this->submitForm([], 'Send Mail');
// Ensure that there is one email in the captured emails array.
$capturedEmails = $this->getEmails();
$this->assertCount(1, $capturedEmails, 'One email was captured.');
}
}