Send transactional emails related to user account in HTML format with Drupal 8

A letter

We have several solutions to automatically send emails emitted by a Drupal 8 project in HTML format. Without being able to quote them all, we can use SwiftMailer, MimeMail to send mails from the server itself, or Mailjet API, MailGun, etc. to send emails from a third-party platform. In a few clicks, we can then emit different emails, whether they are transactional (account creation, order creation, subscription, etc.) or business (Newsletters, Activity Log, What you missed, etc. .), in HTML format. It will then remain to implement one (or more) responsive email template that will be correctly read and interpreted on most mail software. And it's probably the most important part.

But some of these solutions can postulate that the content of these emails must be considered healthy and secure. And to do this these solutions check that the email's content corresponds to a checked markup, filtered and expurgated from possible XSS injections or whatnot. And if not, then they apply a filter that transforms any HTML tag into text, rendering your HTML formatting inoperative. This is the case for example of the SwifMailer module.

Deuce.

For example, you can find this little treatment on the content of an email, before formatting or rendering.

/**
 * Massages the message body into the format expected for rendering.
 *
 * @param array $message
 *   The message.
 *
 * @return array
 *   The message array with the body formatted.
 */
public function massageMessageBody(array $message) {
  // Get default mail line endings and merge all lines in the e-mail body
  // separated by the mail line endings. Keep Markup objects and escape others
  // and then treat the result as safe markup.
  $line_endings = Settings::get('mail_line_endings', PHP_EOL);
  $message['body'] = Markup::create(implode($line_endings, array_map(function ($body) {
    // If $item is not marked safe then it will be escaped.
    return $body instanceof MarkupInterface ? $body : Html::escape($body);
  }, $message['body'])));
  return $message;
}

Quite simply, if the content of the email is not a Markup instance (and thus filtered and expurgated from potentially dangerous strings), then we escape all the HTML tags.

Fortunately, most content that is sent by email come usually from the content of the site itself, content that will be passed through the caudine forks, and intractable, of Drupal 8, and will be presented to the mail formatting system as being verified markup and therefore healthy.

There remains a special case: that of all the emails emitted according to the different events related to the life of a user account (Creation, Waiting for approval, Deletion, Welcome Mail, etc.). All these little emails that we can configure from the Drupal 8 account management interface.

Gestion des emails utilisateur

And because these different messages are related to the configuration of a Drupal 8 site, they are saved as a simple string of characters.

And when they come to the formatting system sending emails, as the content is a simple string of characters, so unverified, the formatting applies its security measure and escapes all the HTML tags that you could have used. So must we have to resort to a Drupal 8 expert here to solve this thorny problem?

Fortunately we have a simple solution. Before the string is sent to the plugin responsible for formatting the email, we just need to intervene upstream, to transform these simple character strings to an instance of MarkupInterface.

Using a small module, implement hook_mail_alter() :

/**
 * Implements hook_mail_alter().
 */
function MYMODULE_mail_alter(&$message) {
  switch ($message['key']) {
    case 'page_mail':
    case 'page_copy':
    case 'cancel_confirm':
    case 'password_reset':
    case 'register_admin_created':
    case 'register_no_approval_required':
    case 'register_pending_approval':
    case 'register_pending_approval_admin':
    case 'status_activated':
    case 'status_blocked':
    case 'status_canceled':
      $message['headers']['Content-Type'] = 'text/html; charset=UTF-8; format=flowed; delsp=yes';
      foreach ($message['body'] as $key => $body) {
        $message['body'][$key] = new FormattableMarkup($body, []);
      }
      break;

  }
}

And for each of the transactional emails related to the life of a user account, we transform these strings of their body into an instance of FormattableMarkup. Note in passing that we can also change the email header to directly specify that the mail will be in HTML format, making it unnecessary to use a module just for that. But it is quite rare on a Drupal 8 project to only have to deal with this mail types.

A simple but effective solution for sending basic emails from a Drupal 8 site in an acceptable format. A small hook that any Drupal 8 developer can put in his basic package. 

 

Commentaires

Ajouter un commentaire