I had to implement a custom email builder. All works fine if the emails went to one recipient, but when I adding multiple recipients the system throws this error:
"mail@test.com, mail2@test.com" does not comply with addr-spec of RFC 2822"
(that is the error message if there are spaces after the commas; otherwise a different system level message is given)

This is very similar to issue 3254085, "Sending mails to multiple email addresses does not work via BC", but that one was for BC or legacy mode, versus this one here is for a custom email handler (EmailBuilder).

And multiple recipients do in fact work with legacy_mode, but not for a custom EmailBuilder. (Sidenote: I suspect that some other EmailBuilders that ship with this module *may* have the same problem, as only some have constructors that take a MailerHelper class, like LegacyEmailBuilder).

I am writing this up, so that maybe somebody else who has the problem can find the solution faster.

The basic solution was this: you cannot pass the multiple addresses as a string (as we used to with hook_mail), but it has to be an array of "Addresses" (Drupal\symfony_mailer\Address ).

There is already a function in MailerHelper called "parseAddresses" that does exactly that.

So in my case, I called that code from by my EmailBuilder->build() routine to process the recipients [you may be able to do this more upstream in fromArray or createParams, etc. ], and that solved the problem.

What I would suggest for this module, is that this recipient processing should be done automatically as part of implementing an EmailBuilder, maybe as part of the base class (EmailBuilderBase), or possibly using MailerHelperTrait.

Or if that is not an option, at the very least it should state this in the documentation for custom EmailBuilders.

Comments

hoporr created an issue. See original summary.

hoporr’s picture

Title: EmailBuilder: Sending to multiple addresses failes » EmailBuilder: Sending to multiple addresses does not work
Issue summary: View changes
adamps’s picture

Category: Feature request » Support request
Status: Active » Postponed (maintainer needs more info)
Issue tags: -symfony_mailer, -emails

It should already work as you request. Please give the code you had that wasn't working.

hoporr’s picture

Below is the code that ended up working [ where MODULENAME is the name of the module ]

It is called like this:
$mailManager = \Drupal::service('plugin.manager.mail');
$result = $mailManager->mail('MYMODULE', $template_key, $recipient, $langcode, $params, $scaf_from_email, TRUE);

where $recipient failed if it was something like "a@a.com, b@b.com"

I had to insert the lines in the build() function

$to = $email->getParam('to');
$address = $this->parseAddress($to);
$email->setTo( $address )

;

Here is the whole code:

/**
 * Defines the Email Builder plug-in for user module, used by symfony_mailer
 *
 * @EmailBuilder(
 *   id = "MYMODULE",
 *   common_adjusters = {"email_subject", "email_body", "email_to"},
 *   import = @Translation("Update messaging"),
 * )
 */
class MYMODULEmailBuilder extends EmailBuilderBase {
  
  use MailerHelperTrait;
  use TokenProcessorTrait;
  
  
  /**
   * Saves the parameters for a newly created email.
   *
   * @param \Drupal\symfony_mailer\EmailInterface $email
   *   The email to modify.
   */
  public function createParams( EmailInterface $email, 
                                $body = NULL, 
                                $sender = NULL, 
                                $recipient = NULL, 
                                $subject = NULL, 
                                $params = NULL) {

    $email->setParam('params',$params);
    $email->setParam('to', $recipient);
    $email->setParam('subject', $subject);
    $email->setParam('body', $body);
    $email->setParam('from', $sender);
  }
  
  /**
   * {@inheritdoc}
   */
  public function fromArray(EmailFactoryInterface $factory, array $message) {
    $sender    = $message['params']['from'];
    $body      = $message['params']['body'];
    $recipient = $message['to'];
    $subject   = $message['params']['subject'];
    
    $params    = $message['params'];
       
    return $factory->newTypedEmail($message['module'], $message['key'],
                                    $body,
                                    $sender,
                                    $recipient,
                                    $subject,
                                    $params );
    
  }
  
  /**
   * {@inheritdoc}
   */
  public function build(EmailInterface $email) {
   
    $to = $email->getParam('to');
    $address = $this->parseAddress($to);
    $email->setTo( $address );

    $email->setSubject($email->getParam('subject'));
    
    // This is how we pass the params to the template
    $email->setVariable('params', $email->getParam('params') );
  }
  
  
  /**
   * Taken from symfony_mailer\MailerHelper
   * 
   * {@inheritdoc}
   */
  public function parseAddress(string $encoded, string $langcode = NULL) {
    foreach (explode(',', $encoded) as $part) {
      // Code copied from \Symfony\Component\Mime\Address::create().
      if (strpos($part, '<')) {
        if (!preg_match(self::FROM_STRING_PATTERN, $part, $matches)) {
          throw new \InvalidArgumentException("Could not parse $part as an address.");
        }
        $addresses[] = new Address($matches['addrSpec'], trim($matches['displayName'], ' \'"'), $langcode);
      }
      else {
        $addresses[] = new Address($part, NULL, $langcode);
      }
    }
    return $addresses ?: [];
  }
}
adamps’s picture

Thanks I understand now.

Some background on addresses:

  1. An address like "a@a.com, b@b.com" is an encoded address - that's how it would be sent across the Internet. It requires a special syntax using quotes, less-than/greater-than and some characters must be encoded.
  2. Symfony Mailer library API requires an array of Address class (Symfony\Component\Mime\Address.php) - this contains information in structured format suitable for use in code or from configuration.

Here is how this module currently works:

  1. This module has "Mailer Policy GUI" that collects data from the admin user, saves config in structured form (email and display name separately) and converts to Address class.
  2. Symfony Mailer library converts this to encoded format.

So basically you are trying to put encoded data where a class is expected. Please can you explain where your address string "a@a.com, b@b.com" comes from? That code should instead be using an Address.

parseAddress() has this comment

   * This function should only be used for back-compatibility and migration,
   * when old code has already encoded the addresses to a string. This function
   * converts back to human-readable format, ready for the symfony mailer
   * library to encode once more during sending! New code should store
   * configuration in human-readable format with a list of addresses with
   * display names.
adamps’s picture

Status: Postponed (maintainer needs more info) » Active
hoporr’s picture

It is built explicitly:

$recipient = from_email . ',' . $partner_email;
...
$mailManager = \Drupal::service('plugin.manager.mail');
$result = $mailManager->mail('MYMODULE', $template_key, $recipient, $langcode, $params, $from_email, TRUE);

This is how it worked with swiftmailer, mailsystem and hook_mail, and the comment above parseAddress() basically states that.

I certainly can move the Address part up to the caller.

However, my point was, when I ported the code this was not documented. If it had not been for that patch in the issue about the legacy mailer, I would not have found this. Other people going through this process may face the same issue.

For that reason, it should be at least documented somewhere (like in the "how to port from swiftmailer" article), or even better, catch this somehow in the baseclass so that it is transparent.

adamps’s picture

Component: Code » Documentation
Category: Support request » Task
Status: Active » Needs review
StatusFileSize
new2.03 KB
new2.03 KB

OK then you totally misunderstood what I was trying to explain with that comment😃.

Sure, I'm willing to take a little time to make things clearer. I've added a table row in https://www.drupal.org/docs/contributed-modules/symfony-mailer-0/develop..., and here is a patch that updates the comments.

adamps’s picture

Title: EmailBuilder: Sending to multiple addresses does not work » Improve comments for sending to multiple addresses
hoporr’s picture

comments make it clearer. Reviewed and tested by me. Thank you.

adamps’s picture

Status: Needs review » Fixed

Thanks

  • AdamPS committed ed76ad56 on 1.x
    Issue #3387961 by AdamPS: Improve comments for sending to multiple...

Status: Fixed » Closed (fixed)

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