Need help migrating SwiftMailer templates

Created on 11 August 2023, over 1 year ago
Updated 13 October 2023, about 1 year ago

I'm struggling to replace SwiftMailer with SymfonyMailer in a Drupal 9.5 site.

With SwiftMailer, I had several twig templates. The one I'm trying first was named: swiftmailer--my-module--released.html.twig.

At this point, I have:

  • installed SymfonyMailer
  • enabled & imported all overrides
  • uninstalled SwiftMailer, SendGrid Integration, and Mail System
  • created a DNS Transport (for SendGrid API key)
  • successfully sent a test email to myself (via the Test tab)

I then tried a feature of the site that sends a notification when a container is released (see the above template name).

Without renaming the template, and with no code modifications, I tried to send the notification. I got an email with the correct subject, but a blank body - because the body is rendered from the template, and it couldn't find the template.

I then renamed the template to email--my-module--released.html.twig. The next email I got had a body, but it was only the static text from the template - none of the dynamic data passed in "params" was showing. But at least it seemed to be finding/using the correct template.

The end of the top line should have the recipient company's name.
Under the column headings, there should be three containers.
There should be text to the right of "Delivery Terms".
There should be two blocks, "Pick-up Location" and "Delivery Location", under "Delivery Terms".

This is the code used to invoke the above email:

    $emailKey    = 'released';    //Used to pick correct email template
    $mailManager = \Drupal::service('plugin.manager.mail');
    $mailManager->mail('my_module', $emailKey, $to, $this->language, $notificationData);

The data that should appear in the email is passed via $notificationData, which ends up in the "params" element of the message array. So it seems the template is not able to "see" the parameters passed to the mail method.

After digging into it, I found the params data a bit deeper in the array structure:

$email = {Drupal\symfony_mailer\Email}
    account = null
    addresses = {array[5]}
    body = {array[0]}
    configFactory = {Drupal\Core\Config\ConfigFactory}
    entity = null
    entityTypeManager = {Drupal\Core\Entity\EntityTypeManager}
    errorMessage = null
    inner = {Symfony\Component\Mime\Email}
    langcode = null
    libraries = {array[0]}
    mailer = {Drupal\symfony_mailer\Mailer}
    params = {array[2]}
        __disable_customize__ = true
        legacy_message = {array[8]}
            id = "sunland_notifications_released"
            key = "released"
            langcode = "en"
            module = "sunland_notifications"
            params = {array[16]}
                additional_details = ""
                attachments = {array[1]}
                containers = {array[0]}
                customer_link = {Drupal\Core\GeneratedLink}
                customer_name = "Sweetener Supply Corp"
                customer_nid = "2595"
                delivery = {array[3]}
                files = {array[6]}
                invoice_nids = {array[0]}
                recipients = {array[5]}
                recipients_count = {int} 2
                release_nid = "479478"
                release_title = "Release 16870"
                release_url = "http://sunland.debug/sales/releases/release-16870"
                subject = "Warehouse Release #16870: Sweetener Supply Corp 0    -  @ Transcor"
                warehouse = {array[4]}
            reply-to = null
            send = true
            to = "doug.fowler@codigovision.com"
    phase = {int} 1
    processors = {array[4]}
    renderer = {Drupal\Core\Render\Renderer}
    sender = null
    subject = null
    subjectReplace = null
    subType = "released"
    theme = ""
    themeManager = {Drupal\Core\Theme\ThemeManager}
    transportDsn = ""
    type = "sunland_notifications"
    variables = {array[0]}

So the actual data I passed in ended up in a "params" element under a "legacy_message" element, which is itself under the top-most "params" element. I don't see how this was ever expected to handle "legacy" code with no modifications.

I then tried to do it the new (SymfonyMailer) way. I changed the code to this:

    $emailFactory = \Drupal::service('email_factory');
    $emailFactory->newTypedEmail('my_module', 'released')
      ->setTo($to)
      ->setSubject($notificationData['subject'])
      ->send();

Note the absence of a third arg for "newTypedEmail". More on that below.
The above code gives this error:

AssertionError: assert($legacy_message != NULL) in assert() (line 96 of modules/contrib/symfony_mailer/src/Plugin/EmailBuilder/LegacyEmailBuilder.php).

I then tried adding $notificationData as the third arg - I got this:

TypeError: Failed to create closure from callable: function "_mail" not found or invalid function name in Closure::fromCallable() (line 416 of core/lib/Drupal/Core/Extension/ModuleHandler.php).

In addition to that error, after refreshing the page, I saw "Undefined array key..." warnings for "reply-to", "key", "params", and "module".

I then changed that third arg to be prefixed with "..." (from examples I had seen in the module code):

    $emailFactory = \Drupal::service('email_factory');
    $emailFactory->newTypedEmail('sunland_notifications', 'released', ...$notificationData)
      ->setTo($to)
      ->setSubject($notificationData['subject'])
      ->send();

That resulted in:

Error: Unknown named parameter $release_nid in Drupal\symfony_mailer\EmailFactory->initEmail() (line 92 of modules/contrib/symfony_mailer/src/EmailFactory.php).

...which happens to be the first element in the data array ($notificationData).

The code in my template is fairly complex - rendering a table inside a for-loop - so I get the feeling it's too involved to be defined in the "body" element of a Policy in SymfonyMailer. Basically, I need to know how to get SymfonyMailer to find the correct twig template, and pass the necessary data to it in the appropriate "params" element. Any guidance in accomplishing that would be greatly appreciated.

๐Ÿ’ฌ Support request
Status

Fixed

Version

1.3

Component

Code

Created by

๐Ÿ‡บ๐Ÿ‡ธUnited States ExTexan

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

Comments & Activities

  • Issue created by @ExTexan
  • Status changed to Fixed over 1 year ago
  • ๐Ÿ‡ฌ๐Ÿ‡งUnited Kingdom adamps

    The documentation we have is here https://www.drupal.org/docs/contributed-modules/drupal-symfony-mailer/mi... โ†’ however I guess you have mostly gone beyond that.

    swiftmailer passed $message to the template, but this module does not because $message is the old mailer API and this module has a new one.

    When you try to do it the new way, you need to write an email builder. Because you have hook_mail it's trying to do automatic conversion from the old API.

    The body element can do anything a template can do.

    Marking as fixed, because that's the limit of how much of my time I wish to give you for free๐Ÿ˜ƒ.

  • ๐Ÿ‡ฉ๐Ÿ‡ฐDenmark FrittenKeeZ

    Marking as fixed, because that's the limit of how much of my time I wish to give you for free๐Ÿ˜ƒ.

    Seeing as the documentation is clearly lacking, both in terms of migrating from Swiftmailer and how to use Twig and custom params with the new EmailBuilder, I can't possible think of a reason why you would mark this as fixed - you might as well just close this issue as you don't seem interested in getting that functionality to work.

  • ๐Ÿ‡ฉ๐Ÿ‡ฐDenmark FrittenKeeZ

    @ExTexan I managed to get it working by manually rendering the twig files and parse the markup along to body within hook_mail.

    $module_path = \Drupal::service('extension.list.module')->getPath('my_module');
    $template = $module_path . '/templates/email-body--' . str_replace('_', '-', $key) . '.html.twig';
    $markup = twig_render_template($template, ['message' => compact('params'), 'theme_hook_original' => 'email_body']);
    $message['body'] = [$markup];
    $message['format'] = 'text/html';
    

    Then I added the wrapper template file which sole content is {{ body | raw }}

    /**
     * Implements hook_theme().
     */
    function my_module_theme($existing, $type, $theme, $path) {
      // Copied from email-wrap theme hook.
      return [
        'email_wrap__my_module' => [
          'template' => 'email-wrap--my-module',
          'variables' => [
            'email' => NULL,
            'body' => '',
            'is_html' => TRUE,
          ],
        ],
      ];
    }
    
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States ExTexan

    @FrittenKeeZ,

    Thanks for the helpful posts. Seeing your messages here made me realize I had neglected to come back and post the solution we settled on.

    I added this line at the top of all twig templates:

    {% set params = _context.email.params.legacy_message.params %}
    

    Then in the rest of the template, I changed all occurrences of "message.params.my_field" to "params.my_field".

    No other code changes were needed elsewhere in the site.

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

  • Status changed to Fixed about 1 year ago
  • ๐Ÿ‡ฎ๐Ÿ‡ณIndia jaspreet21

    @ExTexan,

    Thanks for the code and the explanation, can you please enlighten with more detail on

    {% set params = _context.email.params.legacy_message.params %}
    

    Usage of this above and how are you sending email now.

    My understanding is you have used this above set parms in the Twig you were already using but not just have renamed them or changed the 'swiftmailer' prefix to 'email' to accommodate the new change.

    How ever I am not sure which mechanism are you using to send the emails now and also where have you printed this $email = {Drupal\symfony_mailer\Email}

    Please help, thanks.

  • ๐Ÿ‡น๐Ÿ‡ณTunisia A-Marwen

    in case someone wants to pronts the parameters or the webfom fields from inside the email.html.twig add this preprocess to yout .theme file :

    /**
     * Implements hook_preprocess_HOOK
     */
    function d10_preprocess_email(&$variables) {
    
      /** @var Drupal\symfony_mailer\Email $email*/
     $email = $variables['email'];
    
       // {% set params = _context.email.params.legacy_message.params %}
      /** @var Drupal\webform\Entity\WebformSubmission $webform_submission */
      $params = $email->getParam('legacy_message')['params'];
      $webFormSubmission = $params['webform_submission'];
    
     // collecting the webform fields into an array.
     $variables['params'] = $webFormSubmission->getData();
     $variables['form'] = $webFormSubmission->getData();
    }
    

    then in the email.html.twig just use form.{webformfieldName} EX :

    <ul>
      <li>{{ form.entreprise_email }}</li>
      <li>{{ form.message }}</li>
      <li>{{ form.domain }}</li>
    </ul>
Production build 0.71.5 2024