Allow EmailBuilder plugin not linked to module name or config entity

Created on 19 May 2022, over 3 years ago
Updated 9 March 2023, over 2 years ago

Problem/Motivation

Allow creating a Symfony Mailer EmailBuilder plugin / policy that is not hardwired to a module or config entity?

Proposed resolution

I propose a fix as follows:

  1. Add a label to the EmailBuilder annotation, which can be omitted if the plugin ID is an entity type or module name
  2. Add a boolean setting proxy to the EmailBuilder annotation, which should be set for any builder proxied on behalf of another module
  3. Fix EmailBuilderManager::processDefinition(). The code to set $definition['provider'] is only needed for proxy definitions.
  4. Rename EmailFactoryInterface::sendModuleEmail(), to sendTypedEmail(string $type).

Original issue:

I have a Drupal project with various custom emails, using the hook_mail() logic from Drupal core.
I have enabled symfony_mailer_bc.module and they seem to work.

However, I am trying to change my custom code to use a native Symfony Mailer implementation, so I can disable symfony_mailer_bc.module in the long run.

But I'm struggling to find good examples / documentation.

I my module I have 2 separate email flows (with different params), so I am creating 2 EmailBuilder plugins:


namespace Drupal\ipv_waiting_list\Plugin\EmailBuilder;

use Drupal\commerce_product\Entity\ProductVariationInterface;
use Drupal\symfony_mailer\EmailInterface;
use Drupal\symfony_mailer\MailerHelperTrait;
use Drupal\symfony_mailer\Processor\EmailBuilderBase;
use Drupal\user\UserInterface;

/**
 * Defines the Email Builder plug-in for ipv_waiting_list module.
 *
 * @EmailBuilder(
 *   id = "ipv_waiting_list",
 *   sub_types = {
 *     "lesson_max_capacity" = @Translation("Email informing lesson reached maximum capacity - notification to coordinators and secretariat"),
 *     "participant" = @Translation("Email to participant on waiting list"),
 *   },
 *   common_adjusters = {"email_subject", "email_body", "email_skip_sending"},
 * )
 */
class WaitingListEmailBuilder extends EmailBuilderBase {

  use MailerHelperTrait;

  /**
   * Saves the parameters for a newly created email.
   *
   * @param \Drupal\symfony_mailer\EmailInterface $email
   *   The email to modify.
   * @param \Drupal\user\UserInterface|null $user
   *   The (optional) mail receiving user.
   * @param \Drupal\commerce_product\Entity\ProductVariationInterface|null $lesson
   *   The (optional) lesson object.
   */
  public function createParams(EmailInterface $email, UserInterface $user = NULL, ProductVariationInterface $lesson = NULL) {
    assert($user != NULL);
    assert($lesson != NULL);
    $email->setParam('user', $user);
    $email->setParam('lesson', $lesson);
  }

  /**
   * {@inheritdoc}
   */
  public function build(EmailInterface $email) {
    $email->setTo($email->getParam('user'));
  }

}

and


namespace Drupal\ipv_waiting_list\Plugin\EmailBuilder;

use Drupal\commerce_product\Entity\ProductVariationInterface;
use Drupal\symfony_mailer\EmailInterface;
use Drupal\symfony_mailer\MailerHelperTrait;
use Drupal\symfony_mailer\Processor\EmailBuilderBase;

/**
 * Defines the Email Builder plug-in for waiting list confirmation message.
 *
 * @EmailBuilder(
 *   id = "ipv_waiting_list_confirmation",
 *   sub_types = {
 *     "confirmation" = @Translation("Email confirming registration on waiting list"),
 *   },
 *   common_adjusters = {"email_subject", "email_body", "email_skip_sending"},
 * )
 */
class WaitingListConfirmationEmailBuilder extends EmailBuilderBase {

  use MailerHelperTrait;

  /**
   * Saves the parameters for a newly created email.
   *
   * @param \Drupal\symfony_mailer\EmailInterface $email
   *   The email to modify.
   * @param string|null $recipient
   *   The (optional) mail receiving user.
   * @param \Drupal\commerce_product\Entity\ProductVariationInterface|null $lesson
   *   The (optional) lesson.
   */
  public function createParams(EmailInterface $email, string $recipient = NULL, ProductVariationInterface $lesson = NULL) {
    assert($recipient != NULL);
    $email->setParam('recipient', $recipient);
    $email->setParam('lesson', $lesson);
  }

  /**
   * {@inheritdoc}
   */
  public function build(EmailInterface $email) {
    $email->setTo($email->getParam('recipient'));
  }

}

Now when I go to /admin/config/system/mailer/policy/add, I expect to see both in the Type dropdown, but I only see the module name. When I select that, I see the 2 subtypes of my WaitingListEmailBuilder class. No sign of the WaitingListConfirmationEmailBuilder type / subtype.

Is there only 1 EmailBuilder per module possible? Doesn't seem so, because symfony_mailer_bc contains multiple... Not sure why I'm not seeing them both though...

When I then select a subtype of WaitingListEmailBuilder and try to add a Body element, I get PHP notices and cannot proceed:

Notice: Undefined index: content in /app/web/modules/contrib/symfony_mailer/src/Plugin/EmailAdjuster/BodyEmailAdjuster.php on line 56

Notice: Trying to access array offset on value of type null in /app/web/modules/contrib/symfony_mailer/src/Plugin/EmailAdjuster/BodyEmailAdjuster.php on line 61

Also, I'm not sure what to replace the code with, that used to call the Drupal core mail.manager.
I now have:

        /** @var \Drupal\symfony_mailer\EmailFactoryInterface $email_factory */
        $email_factory = \Drupal::service('email_factory');
        $email_factory->newModuleEmail('ipv_waiting_list_confirmation', 'confirmation')
          ->setParam('recipient', $entity->getEmail())
          ->setParam('commerce_product_variation', $this->lesson)
          ->send();

However I'm not sure if the module name or the type is needed for the first parameter. Seems the type should work, but why does this imply it's for a specific module? Maybe there should be a general $email_factory->newEmail()? Although that's probably nit-picking if I know I can use newModuleEmail().

It would seem to me this is a pretty basic use case? What am I doing wrong exactly? Am I missing some point?

I have checked the doc page https://www.drupal.org/docs/contributed-modules/symfony-mailer-0/develop... β†’ but that seems to be aimed towards letting my module work with symfony_mailer_bc, whereas I want it to work natively...

✨ Feature request
Status

Fixed

Version

1.0

Component

Code

Created by

πŸ‡§πŸ‡ͺBelgium svendecabooter Gent

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

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • πŸ‡ΊπŸ‡ΈUnited States mrweiner

    Could somebody clarify what the annotations should look like when there are multiple EmailBuilders for a single module, and what the sendTypedEmail() implementation should look like? I'm having trouble piecing it together from this issue and the documentation doesn't cover it as far as I can tell.

Production build 0.71.5 2024