Programmatically sending emails with attachments in Drupal 10 using Symfony Mailer
Tags: php, drupal, tutorial, webdev
Published: 2024-04-08 04:00:00

Drupal Symfony Mailer is a module that introduces a new mail system based on the Symfony Mailer, the successor to the Swift Mailer library. It offers full support for HTML emails, file attachments, embedded images, and more.

This article details how to send HTML emails with attachments in Drupal programmatically. Let's assume you need to send a PDF file attached to an email. The PDF file can be stored as a media document, but depending on your requirements it could also be any file stored for example in a field of some content type.

## Installation and Setup

The first step is to install and enable the [Drupal Symfony Mailer](https://www.drupal.org/project/symfony_mailer) module:

```bash
composer require 'drupal/symfony_mailer:^1.4'
vendor/bin/drush en symfony_mailer
```

For this demonstration, a custom module named `mailer_example` is created and enabled as well.

## Creating a Custom Email Builder

In Drupal Symfony Mailer, the `EmailBuilder` is a plugin responsible for constructing the content of specific email types. It allows you to dynamically define the email's subject, body, available variables, and other settings based on the type of email being sent.

To create a custom EmailBuilder, you need to extend `Drupal\symfony_mailer\Processor\EmailBuilderBase` and override the build method:

```php
<?php declare(strict_types=1);

namespace Drupal\mailer_example\Plugin\EmailBuilder;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\file\Entity\File;
use Drupal\file\FileInterface;
use Drupal\media\Entity\Media;
use Drupal\symfony_mailer\Annotation\EmailBuilder;
use Drupal\symfony_mailer\EmailInterface;
use Drupal\symfony_mailer\Processor\EmailBuilderBase;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * @EmailBuilder(
 *     id = "mailer_example_builder",
 *     sub_types = {"default"},
 *     label = "Mailer example builder",
 *     common_adjusters = {"email_subject", "email_body",},
 *   )
 */
class ExampleEmailBuilder extends EmailBuilderBase implements ContainerFactoryPluginInterface {

  private const MEDIA_ID = 1;

  public function __construct(array $configuration, $plugin_id, $plugin_definition, private EntityTypeManagerInterface $entityTypeManager, private FileSystemInterface $fileSystem) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
  }

  #[\Override] public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static($configuration, $plugin_id, $plugin_definition, $container->get('entity_type.manager'), $container->get('file_system'),);
  }

  public function build(EmailInterface $email) {
    $file = $this->getMediaFile(self::MEDIA_ID);

    if ($file instanceof FileInterface) {
      $email->attachFromPath($this->fileSystem->realpath($file->getFileUri()));
    }
  }

  private function getMediaFile(int $mediaId): ?File {
    $media = $this->entityTypeManager->getStorage('media')->load($mediaId);

    if ($media instanceof Media) {
      return $this->entityTypeManager->getStorage('file')
        ->load($media->getSource()->getSourceFieldValue($media));
    }

    return NULL;
  }

}
```
Within the `build` method, you can define the email's subject, body, and attach files using `$email->attachFromPath($filepath)`. You can add as many files as needed with this method.

## Adding a New Symfony Mailer Policy

A Mailer Policy defines how emails are sent from your Drupal site. It allows you to configure the email subject, body, set the email priority, and more. 

1. Visit **Configuration > System > Mailer** and click **Add policy**.

2. Choose **Mailer example builder** (the label of your created EmailBuilder) as the type, then click **Add and configure**.

3. Add the desired content for the Subject and Body elements.


![Drupal symfony mailer policy](https://dzhebrak.com/sites/default/files/inline-images/drupal-mailer-policy-1.png)

Remember that you can export the policy and place it in the `config/install` directory of your module.

## Configuring SMTP Transport (Optional)

Depending on your setup, you might also need to add an SMTP transport.

1. Visit **Configuration > System > Mailer > Transport**, select **SMTP**, and click **Add transport**.

2. Specify your SMTP configuration (username, password, host name, port).

I use docker and [`mailhog/mailhog`](https://hub.docker.com/r/mailhog/mailhog) for testing emails in a local development environment, so my configuration looks like:

![Drupal symfony mailer smtp transport](https://dzhebrak.com/sites/default/files/inline-images/drupal-symfony-mailer-transport.png)

* Host name: `mail` (specified in `docker-compose.yml`)
* Port: `1025` (default mailhog SMTP port)

## Sending the Email

Use `email_factory` service for injecting email factory and creating a new email using `newTypedEmail` method:

```php
/** @var \Drupal\symfony_mailer\EmailFactoryInterface $emailFactory */
$emailFactory = \Drupal::service('email_factory');

$email = $emailFactory->newTypedEmail('mailer_example_builder', '');
$email->setTo('test@example.com');
$email->send();
```
The method takes an `EmailBuilder` plugin id (specified in the annotation) and sub-type as arguments.

![Drupal mailhog](https://dzhebrak.com/sites/default/files/inline-images/drupal-mailhog.png)

## Conclusion

The Drupal Symfony Mailer module empowers you to send programmatic emails with flexibility and control. By leveraging EmailBuilder and Mailer Policies, you can create dynamic and customizable email templates with attachments. This approach enhances your email communication's efficiency, maintainability, and content tailoring capabilities.