Handling Emails Asynchronously: Integrating Symfony Mailer and Messenger
Take advantage of Symfony Mailer’s first-class integration with Symfony Messenger brought to Drupal via the SM project, allowing your site to send emails asynchronously.
by
daniel.phin
/ 8 February 2024
This post is part 6 in a series about Symfony Messenger.
- Introducing Symfony Messenger integrations with Drupal
- Symfony Messenger’ message and message handlers, and comparison with @QueueWorker
- Real-time: Symfony Messenger’ Consume command and prioritised messages
- Automatic message scheduling and replacing hook_cron
- Adding real-time processing to QueueWorker plugins
- Making Symfony Mailer asynchronous: integration with Symfony Messenger
- Displaying notifications when Symfony Messenger messages are processed
- Future of Symfony Messenger in Drupal
Since Swift Mailer and its Drupal contrib integration were recently deprecated, many projects have naturally switched to its replacement: Symfony Mailer, either via Drupal Symfony Mailer or Drupal Symfony Mailer Lite.
This post outlines how you can take advantage of Symfony Mailer’s first class integration with Symfony Messenger brought to Drupal via the SM project. This integration allows for dispatching emails off-thread, potentially improving performance of the dispatching (usually web-) thread by offloading email-related tasks to dedicated Symfony Messenger workers. This setup can be considered an alternative to using Queue Mail.
Setup
As of writing, of the two Symfony Mailer implementations in contrib, Drupal Symfony Mailer Lite has built in support for Symfony Messenger. Drupal Symfony Mailer does not yet support it, an issue and merge request exist to add it. Apply a patch until the changes are merged.
Symfony Messenger itself does not require any special configuration, other than installing SM.
To run asynchronously, the \Symfony\Component\Mailer\Messenger\SendEmailMessage
message must have routing configuration to a transport. Or at least the fallback transport must be configured. Without transport configuration, Emails will still be dispatched through Messenger, however they will be executed synchronously in the same thread they were dispatched.
Opting out
If you happen to have both Symfony Mailer and Symfony Messenger installed but do not want emails to be sent asynchronously, you can configure routing for the \Symfony\Component\Mailer\Messenger\SendEmailMessage
message to instead use the synchronous
transport.
If you’re using the SM Config submodule:
Sending emails and dispatching emails
Emails may be dispatched using the usual Drupal mechanism, or you can dispatch using Symfony Mailer directly by constructing an email object:
$email = (new \Symfony\Component\Mime\Email()) ->to('jane@example.com') ->from('john@example.com') ->subject('Hello world!') ->text('Some sample text.') ->html('<p>some <strong>sample</strong> text.</p>');/** @var \Symfony\Component\Mailer\MailerInterface $mailer */$mailer = \Drupal::service(\Symfony\Component\Mailer\MailerInterface::class);$mailer->send($email);
After the send
method is executed, Mailer checks Messenger is available, creates a new SendEmailMessage
message to wrap the \Symfony\Component\Mime\Email
object. Then dispatches SendEmailMessage
to the messenger bus.
As is typical with Symfony Messenger, email messages must be serialisable. Avoid including any Drupal entities or service references in an email object, and render email contents before sending it.
Processing emails
To process email messages, run the worker with sm messenger:consume
. This command will either listen or poll for messages and execute them in a dedicated thread, ensuring quick processing after they are dispatched. For more information on the worker, please refer to post 3 of this series.
In the next post, we’ll explore how to add a user interface to notify users when relevant tasks have been processed.
Tagged