Send email with an attachment without using contrib modules in Drupal 8

Mail Box

Introduction

When we send a simple email we need to do two things:

1. Implement hook_mail() in our custom module.

This hook modifes email message right before it gets fully processed by PhpMail class, which is the default Drupal mail backend.
The example implementation is shown below:

/**
 * Implements hook_mail().
 */
function <module_name>_mail($key, &$message, $params) {
  switch ($key) {
    case 'your_special_trigger_key':
      $message['from'] = 'info@site.com';
      $message['subject'] = "Your subject";
      $message['body'][] = $params['message'];      
      break;
  }
}

2. Use Mail Manager to send email.

The Mail Manager is the mail service which can be called from other hooks and functions, depending on our code logic. If we call it from a hook or some function into .module file, for example, it should be called statically, as it shown below, otherwise it must be injected by using a Dependency Injection.

  $mailManager = \Drupal::service('plugin.manager.mail');
  $module = 'your_module_name';
  $key = 'your_special_trigger_key';
  $to = "recepient@mail.com";  
  $params['message'] = "This is our message";  
  $langcode = \Drupal::currentUser()->getPreferredLangcode();
  $send = TRUE;
  $result = $mailManager->mail($module, $key, $to, $langcode, $params, NULL, $send);

How to send an email with attachments programatically?

Most documentation suggests using conrib modules, for example Mail System and Swift Mailer for sending emails with attachments.

However it is possible to send an email with an attachment without using any contrib module.
The major point here is that we need to properly include files's content and its mime headers into message's body, which will become an email with an attachment in this case.

The example below provides this functionality:

  // Put this code into other hook, function, etc.
  $mailManager = \Drupal::service('plugin.manager.mail');
  $module = 'your_module_name';
  $key = 'your_special_trigger_key';
  $to = "recepient@mail.com";  
  $params['message'] = "This is our message";  
  // Here we add our files URIs for an attachment.
  $params['attachments'] = [
      'public://file.pdf',
      'public://image-file.png',
      ];
  $langcode = \Drupal::currentUser()->getPreferredLangcode();
  $send = TRUE;
  $result = $mailManager->mail($module, $key, $to, $langcode, $params, NULL, $send);

The main part here, we create custom _add_attachment() function which modifies the $message variable and then we can call it from hook_mail() in case if there is an attachment:

/**
 * Implements hook_mail().
 */
function <module_name>_mail($key, &$message, $params) {
  switch ($key) {
    case 'your_special_trigger_key':
      $message['from'] = 'info@site.com';
      $message['subject'] = "Your subject";
      $message['body'][] = $params['message'];
      if (!empty($message['params']['attachments'])) {
        // Add an attachments to $message array.
        _add_attachment($message);
      }      
      break;
  }
}

/**
 * Adds attachment files to the message's body.
 *
 * @param array $message
 *   Message array which comes from the Mail Manager.
 */
function _add_attachment(array &$message) {
  $params = $message['params'];
  $mime_id = md5(uniqid(time().rand(),1));
  $headers = &$message['headers'];
  $message_content_type = $headers['Content-Type'];
  $headers['Content-Type'] = "multipart/mixed; boundary=\"$mime_id\"";
  $body = "This is a multi-part message in MIME format.\r\n";
  $body .= "--$mime_id\r\n";
  $body .= "Content-Type: $message_content_type \r\n\r\n";
  $body .= wordwrap(implode('', $message['body'])) . "\r\n\r\n";
  if (!empty($params['attachments'])) {
    foreach ($params['attachments'] as $file) {
      // Here we add the attachment to the message body.
      $file_name = \Drupal::service('file_system')->basename($file);
      $mime_type = \Drupal::service('file.mime_type.guesser')->guess($file);
      $file_content = file_get_contents($file);
      $base64 = chunk_split(base64_encode($file_content));
      $body .= "--$mime_id\r\n";
      $body .= "Content-Transfer-Encoding: base64\r\n";
      $body .= "Content-Type: $mime_type;
 name=$file_name\r\n";
      $body .= "Content-Disposition: attachment;
 filename=$file_name\r\n\r\n";
      $body .= $base64 . "\r\n\r\n";
    }
  }
  $body .= '--' . $mime_id . '--';
  $message['body'] = [$body];
}

Conclusion

The latter example provides simple working solution for sending emails with an attachments without using any contrib modules in Drupal 8. Actually, the contrib modules or PHP mail libraries would do the similar changes to the message's body and they offer much more functionality, but our point was to show the processes under the hood.
 

Comments

Submitted by KIRANKUMAR on Tue, 05/28/2019 - 14:16

Hi I tried this code. I have attached a text file. But I am not getting the textfile as attachment but as base64 encoded content inside the mail. Please advice.

Submitted by Thomas Schuh on Tue, 09/10/2019 - 14:15

Thanks for this function. It's working like a charm without any contrib modules. Great!

Add new comment

CAPTCHA
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.