class SymfonyMailer

Same name in other branches
  1. 11.x core/lib/Drupal/Core/Mail/Plugin/Mail/SymfonyMailer.php \Drupal\Core\Mail\Plugin\Mail\SymfonyMailer

Defines an experimental mail backend, based on the Symfony mailer component.

This mail plugin acts as a drop-in replacement for the current default PHP mail plugin. Mail delivery is based on the Symfony mailer component. Hence, all transports registered by default in the Symfony mailer transport factory are available via configurable DSN.

By default, this plugin uses `sendmail://default` as the transport DSN. I.e., it attempts to use `/usr/sbin/sendmail -bs` in order to submit a message to the MTA. Sites hosted on operating systems without a working MTA (e.g., Windows) need to configure a suitable DSN.

The DSN can be set via the `mailer_dsn` key of the `system.mailer` config.

The following example shows how to switch the default mail plugin to the experimental Symfony mailer plugin with a custom DSN using config overrides in `settings.php`:

$config['system.mail']['interface'] = [
    'default' => 'symfony_mailer',
];
$config['system.mail']['mailer_dsn'] = [
    'scheme' => 'smtp',
    'host' => 'smtp.example.com',
    'port' => 25,
    'user' => 'user',
    'password' => 'pass',
    'options' => [],
];

@internal

Hierarchy

Expanded class hierarchy of SymfonyMailer

See also

https://symfony.com/doc/current/mailer.html#using-built-in-transports

1 file declares its use of SymfonyMailer
SymfonyMailerTest.php in core/tests/Drupal/Tests/Core/Mail/Plugin/Mail/SymfonyMailerTest.php

File

core/lib/Drupal/Core/Mail/Plugin/Mail/SymfonyMailer.php, line 55

Namespace

Drupal\Core\Mail\Plugin\Mail
View source
class SymfonyMailer implements MailInterface, ContainerFactoryPluginInterface {
    
    /**
     * A list of headers that can contain multiple email addresses.
     *
     * @see \Symfony\Component\Mime\Header\Headers::HEADER_CLASS_MAP
     */
    protected const MAILBOX_LIST_HEADERS = [
        'from',
        'to',
        'reply-to',
        'cc',
        'bcc',
    ];
    
    /**
     * List of headers to skip copying from the message array.
     *
     * Symfony mailer sets Content-Type and Content-Transfer-Encoding according to
     * the actual body content. Note that format=flowed is not supported by
     * Symfony.
     *
     * @see \Symfony\Component\Mime\Part\TextPart
     */
    protected const SKIP_HEADERS = [
        'content-type',
        'content-transfer-encoding',
    ];
    public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
        return new static($container->get('logger.channel.mail'));
    }
    
    /**
     * Symfony mailer constructor.
     *
     * @param \Psr\Log\LoggerInterface $logger
     *   The logger service.
     * @param \Symfony\Component\Mailer\MailerInterface $mailer
     *   The mailer service. Only specify an instance in unit tests, pass NULL in
     *   production.
     */
    public function __construct(LoggerInterface $logger, ?MailerInterface $mailer = NULL) {
    }
    public function format(array $message) {
        foreach ($message['body'] as &$part) {
            // If the message contains HTML, convert it to plain text (which also
            // wraps the mail body).
            if ($part instanceof MarkupInterface) {
                $part = MailFormatHelper::htmlToText($part);
            }
            else {
                $part = MailFormatHelper::wrapMail($part);
            }
        }
        // Join the body array into one string.
        $message['body'] = implode("\n\n", $message['body']);
        return $message;
    }
    public function mail(array $message) {
        try {
            $email = new Email();
            $headers = $email->getHeaders();
            foreach ($message['headers'] as $name => $value) {
                if (!in_array(strtolower($name), self::SKIP_HEADERS, TRUE)) {
                    if (in_array(strtolower($name), self::MAILBOX_LIST_HEADERS, TRUE)) {
                        // Split values by comma, but ignore commas encapsulated in double
                        // quotes.
                        $value = str_getcsv($value, escape: '\\');
                    }
                    $headers->addHeader($name, $value);
                }
            }
            $email->to($message['to'])
                ->subject($message['subject'])
                ->text($message['body']);
            $mailer = $this->getMailer();
            $mailer->send($email);
            return TRUE;
        } catch (\Exception $e) {
            Error::logException($this->logger, $e);
            return FALSE;
        }
    }
    
    /**
     * Returns a minimalistic Symfony mailer service.
     */
    protected function getMailer() : MailerInterface {
        if (!isset($this->mailer)) {
            $dsn = \Drupal::config('system.mail')->get('mailer_dsn');
            $dsnObject = new Dsn(...$dsn);
            // Symfony Mailer and Transport classes both optionally depend on the
            // event dispatcher. When provided, a MessageEvent is fired whenever an
            // email is prepared before sending.
            //
            // The MessageEvent will likely play an important role in an upcoming mail
            // API. However, emails handled by this plugin already were processed by
            // hook_mail and hook_mail_alter. Firing the MessageEvent would leak those
            // mails into the code path (i.e., event subscribers) of the new API.
            // Therefore, this plugin deliberately refrains from injecting the event
            // dispatcher.
            $factories = Transport::getDefaultFactories(logger: $this->logger);
            $transportFactory = new Transport($factories);
            $transport = $transportFactory->fromDsnObject($dsnObject);
            $this->mailer = new Mailer($transport);
        }
        return $this->mailer;
    }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title
SymfonyMailer::create public static function Creates an instance of the plugin. Overrides ContainerFactoryPluginInterface::create
SymfonyMailer::format public function Formats a message prior to sending. Overrides MailInterface::format
SymfonyMailer::getMailer protected function Returns a minimalistic Symfony mailer service.
SymfonyMailer::mail public function Sends a message composed by \Drupal\Core\Mail\MailManagerInterface->mail(). Overrides MailInterface::mail
SymfonyMailer::MAILBOX_LIST_HEADERS protected constant A list of headers that can contain multiple email addresses.
SymfonyMailer::SKIP_HEADERS protected constant List of headers to skip copying from the message array.
SymfonyMailer::__construct public function Symfony mailer constructor.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.