Add Symfony Messenger support for async messsages (emails as queues)

Created on 14 October 2023, about 1 year ago

Problem/Motivation

Mailer has inbuilt support for messenger, which allows messages to be queued asynchronously. Basically, emails are added to queues. Then run the messenger consume command to process them.

https://symfony.com/doc/current/mailer.html#sending-messages-async

Messenger support is young, but I'm personally involved in getting it out.

I'm attaching a MR which works great with WIP code for Symfony messenger. You wont be able to use the code yet without checking out multiple git MR repos for Symfony Messenger.

Some notes on MR:

  • Adds a selection of services from Symfony Mailer project, in order for Messenger to properly work together.
  • I've tried not to introduce new concepts.
  • Testing uses a real state transport (symfony_mailer_state_transport), I wasn't really happy with what was in symfony_mailer_test, and found it difficult to get working.
  • The code as is uses autowire, however I believe it only works fully in Drupal 10.1. If you want code for 10.0 or less we need to explicitly add many arguments to services. Not really an issue.
  • A factory is used for mailer.transport_factory and mailer.default_transport since I dont think we have the right Symfony service container features to do it with the same method as Symfony itself.
  • There are a bunch of changes for DI for \Drupal\symfony_mailer\Mailer. I'd recommend making this a final/private class, requiring anyone wanting to reference it to alias. Or anyone wanting to override features to use decoration, etc.
  • Support is completely optional. If messenger services are not detected then messenger async does not kick in. The newly introduced mailer transport services remain active however.

Proposed resolution

Implement support as close to Symfony Framework as possible.

Remaining tasks

Wait for symfony messenger code to be pushed publicly.
Figure out if we can make the project, or the branch this is committed to, require D10.1. If not then remove the autowiring changes.
Advice on maintainers re preferences for Mailer \Drupal\symfony_mailer\Mailer back compat.

✨ Feature request
Status

Active

Version

1.4

Component

Code

Created by

πŸ‡¦πŸ‡ΊAustralia dpi Perth, Australia

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

Comments & Activities

  • Issue created by @dpi
  • Open on Drupal.org β†’
    Core: 9.5.x + Environment: PHP 7.4 & MySQL 5.7
    last update about 1 year ago
    Not currently mergeable.
  • @dpi opened merge request.
  • Open in Jenkins β†’ Open on Drupal.org β†’
    Core: 9.5.x + Environment: PHP 7.4 & MySQL 5.7
    last update about 1 year ago
    PHPLint Failed
  • πŸ‡¦πŸ‡ΊAustralia dpi Perth, Australia

    I'd appreciate feedback on the Q's asked in the issue summary.

    Development can resume after I get back here re stability of Messenger code. Though technically could be merged as messenger is an optional dependency.

  • Status changed to Needs review about 1 year ago
  • πŸ‡¦πŸ‡ΊAustralia dpi Perth, Australia

    Prelim review requested per above.

  • πŸ‡¬πŸ‡§United Kingdom adamps

    Thanks @dpi. I didn't yet do a review, however I have some quick remarks.

    1) I think it's a good time to make a 2.x release soon, and it can require D10.1. This issue potentially makes some significant changes so it makes sense to add it in a major branch.

    2) I propose to hold off committing this until the matching symfony code is released and definitely not going to change. However we can develop, review and test it before that.

    3) I plan to move the transports to a separate project mailer_transport in 2.x, allowing use in multiple places. It should also match the development in Core πŸ“Œ [PP-1] Add symfony mailer transports to DIC Postponed , although the current Core MR might need adjusting to work with this module.

  • πŸ‡¦πŸ‡ΊAustralia dpi Perth, Australia

    1) I think it's a good time to make a 2.x release soon, and it can require D10.1.

    Perfect. 2.x with new deps sounds good.

    I propose to hold off committing this until the matching symfony code

    Absolutely, I'll update as I progress.

    I plan to move the transports to a separate project mailer_transport

    Ah!

    Then I might suggest borrowing and utilising a bunch of the mailer classes I added in this MR for this kind of decoupling.

  • πŸ‡¬πŸ‡§United Kingdom adamps

    One other thought - if this issue will make widespread changes then perhaps we need to split it into more issues.

    We already have πŸ“Œ Consider marking some classes/functions @internal and find more convenient ways of dependency injection Active .

  • πŸ‡¦πŸ‡ΊAustralia dpi Perth, Australia

    I think you can introduce the services on their own.

    That would effectively be the majority of the functional code in this MR: Services.yml, Connections, TransportFactoryFactory, TransportsFactory

    The other half would be split out: the Mailer class change, and two services: mailer.messenger.message_handler + mailer.mailer

    50% of the code for this MR is tests for messenger. So it would be slimmed down further.

  • Open in Jenkins β†’ Open on Drupal.org β†’
    Core: 9.5.x + Environment: PHP 7.4 & MySQL 5.7
    last update about 1 year ago
    Composer require-dev failure
  • Open in Jenkins β†’ Open on Drupal.org β†’
    Core: 10.1.x + Environment: PHP 8.1 & MySQL 8
    last update about 1 year ago
    Composer require-dev failure
  • Open in Jenkins β†’ Open on Drupal.org β†’
    Core: 9.5.x + Environment: PHP 7.4 & MySQL 5.7
    last update about 1 year ago
    Composer require-dev failure
  • Open in Jenkins β†’ Open on Drupal.org β†’
    Core: 10.1.x + Environment: PHP 8.2 & MySQL 8
    last update about 1 year ago
    Composer require-dev failure
  • πŸ‡¦πŸ‡ΊAustralia dpi Perth, Australia

    Created an issue for the services pieces πŸ› Add services/container features from upstream Symfony Mailer component Active . The code will continue to be duplicated in this issue as it is a requirement.

  • πŸ‡¦πŸ‡ΊAustralia dpi Perth, Australia

    The Symfony Messenger and Doctrine transport β†’ code has been pushed publicly. You should be able to review it locally now.

    Gitlab CI doesnt have a 10.1 runner, so its not going to work with that for now.

    Keep in mind there are two core patches required. The patching stuff works great on Gitlab (see Gitlab ymls' for SM), I'm not sure if it works with DrupalCI. You might be interested in borrowing matrix support from SM in order to test multiple core/php/database-engines.

    Back to needs review w/ @AdamPS

  • πŸ‡¦πŸ‡ΊAustralia dpi Perth, Australia

    ^Yeah DCI gets stuck on allow plugins. Just wait for a D10.1 + Gitlab setup.

  • πŸ‡¦πŸ‡ΊAustralia dpi Perth, Australia
  • πŸ‡¬πŸ‡§United Kingdom adamps

    OK I'm still trying to get my head around this. I'll have some high-level comments and questions, probably in some cases from my lack of understanding.

    Mostly I'm confused by why you want to change all these things. Aha maybe I can guess: you want to use DI instead of fixed Symfony\Component\Mailer\Mailer. In fact perhaps this is exactly what the title of the issue should be?? Any maybe it's scheduled for 2.x anyway, as described below? So this issue may come to nothingπŸ˜ƒ???

    1) Changing the transport factory code should be separate from this issue. It would be part of the discussion for creating the new " Mailer Transport β†’ " module. Would you be interested in working together on it?

    Please first of all explain your goal (or maybe I already guessed above).

    We already thought about this code quite hard in previous issues, and we rejected using the Symfony library Transport and Transports classes:

    • They seem to rely on a static array of transports known from configuration, which prevents setting the transport DSN programmatically. The new Connections class will not actually work for the same reason.
    • The use of an "X-Transport" seems clumsy and unnecessary in our case.

    Perhaps we could create our own equivalent of Transports?? We can extend the symfony Email class with one that includes a getTransportDsn() method, and call that in the TransportInterface::send().

    2) I feel the tests for this module should not depend on drupal/sm. Also probably StateTransport does not belong in this module, and this module should not use patches in composer.json. Instead why not put these tests in the SM module - which I believe contains the code that is actually being tested here.

  • Status changed to Postponed: needs info about 1 year ago
  • πŸ‡¬πŸ‡§United Kingdom adamps
  • Status changed to Active about 1 year ago
  • πŸ‡¬πŸ‡§United Kingdom adamps

    It looks like the main problem is that this module creates the Symfony\Component\Mailer\Mailer object with MessageBusInterface = null. We should instead allow injecting a bus.

    @dpi - please tell me if this meets your requirements.

    In terms of the rest, lets keep them as separate issues

  • πŸ‡¦πŸ‡ΊAustralia dpi Perth, Australia

    Unless someone gets to this before me, I'll be revisiting in the near future. Right now I'm doing this voluntarily, without sponsors or motivation.

  • πŸ‡¦πŸ‡ΊAustralia dpi Perth, Australia

    I think I still want to include a service to access Symfony\Component\Mailer\Mailer instead of burying it in src/Mailer.php. So the container has the responsibility of putting together the object and messenger integration. I'm not particularly fond of an approach where we'd manually checking if messenger is available and inject that.

    As for autowiring and all that, sure that can be removed as it's noisy.

    Some of the factory and connection things are necessary to allow Symfony\Component\Mailer\Mailer to bridge with how this project puts things together. Its not something the core issue ( πŸ“Œ [PP-1] Add symfony mailer transports to DIC Postponed ) will be addressing either, as the core implementation doesn't have legacy from this project, particularly the plugin systems.

  • πŸ‡¦πŸ‡ΊAustralia dpi Perth, Australia

    On Mailer as a service, its quite advantageous to have it available, as you can do things like this without dealing with the legacy mail pipeline.

    $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);
    
  • πŸ‡¬πŸ‡§United Kingdom adamps

    > I'll be revisiting in the near future.
    Great

    > Right now I'm doing this voluntarily, without sponsors
    Same here for this whole moduleπŸ˜ƒ

    > I think I still want to include a service to access Symfony\Component\Mailer\Mailer
    The difficulty is that Mailer requires injection of a single fixed Transport at creation time. This module uses a different transport for each mail. There are some ways to workaround/change that however they are a bit messy. I feel that we shouldn't make any change to that area in this module until Core has decided what it will do.

    > Its not something the core issue ( #3379794: Add symfony mailer transports to Dependency Injection Container ) will be addressing either
    The core issue contains a big discussion (still in progress) of whether to require a single fixed transport. Maybe you have noticed that since you wrote this sentence, as I see that you have commented on the other issueπŸ˜ƒ.

    > On Mailer as a service, its quite advantageous to have it available
    The code in your example is very very wrong if using this moduleπŸ˜ƒ. This module provides a service with Drupal\symfony_mailer\MailerInterface, and a new Email class that should be created using a factory class.

  • πŸ‡¦πŸ‡ΊAustralia dpi Perth, Australia
  • πŸ‡¬πŸ‡§United Kingdom adamps

    I believe the best way is to fix DSM-L and DSM together - having 100s of lines copied between the two modules doesn't really make sense. I created a module to do exactly that: https://www.drupal.org/project/mailer_transport β†’ . I don't really have time at the moment - would you like to be a maintainer??

    Once πŸ“Œ [PP-1] Add symfony mailer transports to DIC Postponed has been committed then that gives the direction we need to follow.

  • πŸ‡¦πŸ‡ΊAustralia dpi Perth, Australia

    I think thats a question for @zengenuity, and perhaps @znerol.

    Selfishly, my motivation is to bring as many people to the Symfony Messenger party. My passion is less in the Mailer systems.

    having 100s of lines copied between the two modules doesn't really make sense

    For now, duplication should be fine til we come up with some long term solutions. In the least, this module and SML are unlikely/shouldn't be used together.

  • πŸ‡¬πŸ‡§United Kingdom adamps

    I propose that this issue belongs in this module.

    In πŸ“Œ Split to use a new Mailer Transport module Active we would copy in lots of existing code.

    The other part is the code that you already started to write for a mailer transport service. It should be adapted to match πŸ“Œ [PP-1] Add symfony mailer transports to DIC Postponed . It would multiplex to the actual transports based on a custom header "mailer transport dsn". It needs to be a DSN rather than a name, because we allow hooks to set the transport to an arbitrary DSN, rather than limiting it to a fixed set of configured named transports.

  • Status changed to Fixed about 2 months ago
  • πŸ‡¬πŸ‡§United Kingdom adamps

    This should be fixed now as part of πŸ“Œ Create separate module mailer_transport Active

  • Automatically closed - issue fixed for 2 weeks with no activity.

Production build 0.71.5 2024