Відправлення листів за допомогою Mailer

Дата оновлення перекладу 2024-06-09

Відправлення листів за допомогою Mailer

Установка

Компоненти Symfony Mailer та Mime формують потужну систему для створення та відправлення електронних листів - з підтримкою складових повідомлень, інтеграцією Twig, вбудовуванням CSS, прикріпленням файлів та багато чим іншим. Встановіть їх за допомогою:

1
$ composer require symfony/mailer

Налаштування транспорту

Листи доставляються за допомогою "транспорту". Одразу після установки, ви можете відправляти листи через SMTP, сконфігурувавши DSN у вашому файлі .env (параметри user, pass та port не обов'язкові):

1
2
# .env
MAILER_DSN=smtp://user:pass@smtp.example.com:port
1
2
3
4
# config/packages/mailer.yaml
framework:
    mailer:
        dsn: '%env(MAILER_DSN)%'

Caution

Якщо ім'я користувача, пароль або хостинг містять в URI будь-який символ, який вважається особливим (такий як : / ? # [ ] @ ! $ & ' ( ) * + , ; =), ви маєте закодувати їх. Див. RFC 3986. щоб побачити повний список зарезервованих символів або використайте функцію urlencode, щоб закодувати їх.

Використання вбудованого транспорту

DSN-???????? ??????? ????
smtp smtp://user:pass@smtp.example.com:25 Mailer ???????????? SMTP-?????? ??? ???????????? ??????
sendmail sendmail://default Mailer ???????????? ???????? ?????????? sendmail ??? ???????????? ??????
native native://default Mailer ???????????? ?????????? sendmail ?? ?????, ?????????????? ? ???????????? sendmail_path ??? php.ini. ?? ????????? Windows, Mailer ???????? ???????????? ???????????? smtp ? smtp_port ??? php.ini, ???? sendmail_path ?? ??????????????.

Caution

При використанні native://default, якщо php.ini використовує команду sendmail -t, у вас не буде звіту про помилки і заголовки Bcc не будуть видалені. Дуже рекомендовано НЕ використовувати native://default, так як ви не можете контролювати, як буде сконфігуровано sendmail (краще використайте sendmail://default, якщо це можливо).

Використання стороннього транспорту

Замість використання вашого власного SMTP-серверу або бінарності binary, ви можете відправляти листи через стороннього постачальника. Mailer підтримує декілька - встановіть той, який захочете:

?????? ????????? ?? ????????? ????????? ???-????
Amazon SES composer require symfony/amazon-mailer  
Azure composer require symfony/azure-mailer  
Brevo composer require symfony/brevo-mailer ???
Infobip composer require symfony/infobip-mailer  
Mailgun composer require symfony/mailgun-mailer ???
Mailjet composer require symfony/mailjet-mailer ???
MailPace composer require symfony/mail-pace-mailer  
MailerSend composer require symfony/mailer-send-mailer  
Mandrill composer require symfony/mailchimp-mailer  
Postmark composer require symfony/postmark-mailer ???
Resend composer require symfony/resend-mailer ???
Scaleway composer require symfony/scaleway-mailer  
SendGrid composer require symfony/sendgrid-mailer ???

7.1

Інтеграції Azure та Resend були представлені в Symfony 7.1.

Note

Задля зручності Symfony також надає підтримку для Gmail (composer require symfony/google-mailer), але це не можна використовувати у виробництві. У розробці, вам, ймовірно, треба використати натомість email catcher . Відмітьте, що більшість підтримуваних провайдерів також пропонує безкоштовний рівень.

Кожна бібліотека містить рецепт Symfony Flex , який буде додавати приклад конфігурації у ваш файл .env. Наприклад, уявіть, що ви хочете використати SendGrid. Спочатку встановіть його:

1
$ composer require symfony/sendgrid-mailer

Тепер у вас буде новий рядок у вашому файлі .env, який ви можете розкоментувати:

1
2
# .env
MAILER_DSN=sendgrid://KEY@default

MAILER_DSN - це не справжня адреса: це зручний формат, який віддає більшу частину роботи конфігурації поштовій програмі. Схема sendgrid активує постачальника SendGrid, який ви щойно встановили, який знає все про те, як доставляти повідомлення через SendGrid. Єдине, що вам необхідно змінити - заповнювач KEY.

Кожний постачальник має різні змінні середовища, які Mailer використовує для конфігурації справжнього протоколу, адреси та аутентифікації для відправки. Деякі також мають опції, які можна сконфігурувати з параметрами запиту в кінці MAILER_DSN - як, наприклад, ?region= для Amazon SES або Mailgun. Деякі постачальники підтримують відправлення через http, api или smtp. Symfony обирає найкращий доступний транспорт, але ви можете форсувати використання одного з них:

1
2
3
# .env
# форсувати використання SMTP замість HTTP (який стоїть за замовчуванням)
MAILER_DSN=sendgrid+smtp://$SENDGRID_KEY@default

Ця таблиця демонструє повний список доступних форматів DSN для кожного стороннього постачальника:

???????????? ???????
Amazon SES
  • SMTP ses+smtp://USERNAME:PASSWORD@default
  • HTTP ses+https://ACCESS_KEY:SECRET_KEY@default
  • API ses+api://ACCESS_KEY:SECRET_KEY@default
Azure
  • API azure+api://ACS_RESOURCE_NAME:KEY@default
Brevo
  • SMTP brevo+smtp://USERNAME:PASSWORD@default
  • HTTP n/a
  • API brevo+api://KEY@default
Google Gmail
  • SMTP gmail+smtp://USERNAME:APP-PASSWORD@default
  • HTTP n/a
  • API n/a
Infobip
  • SMTP infobip+smtp://KEY@default
  • HTTP n/a
  • API infobip+api://KEY@BASE_URL
Mandrill
  • SMTP mandrill+smtp://USERNAME:PASSWORD@default
  • HTTP mandrill+https://KEY@default
  • API mandrill+api://KEY@default
MailerSend
  • SMTP mailersend+smtp://KEY@default
  • HTTP n/a
  • API mailersend+api://KEY@BASE_URL
Mailgun
  • SMTP mailgun+smtp://USERNAME:PASSWORD@default
  • HTTP mailgun+https://KEY:DOMAIN@default
  • API mailgun+api://KEY:DOMAIN@default
Mailjet
  • SMTP mailjet+smtp://ACCESS_KEY:SECRET_KEY@default
  • HTTP n/a
  • API mailjet+api://ACCESS_KEY:SECRET_KEY@default
MailPace
  • SMTP mailpace+api://API_TOKEN@default
  • HTTP n/a
  • API mailpace+api://API_TOKEN@default
Postmark
  • SMTP postmark+smtp://ID@default
  • HTTP n/a
  • API postmark+api://KEY@default
Resend
  • SMTP resend+smtp://resend:API_KEY@default
  • HTTP n/a
  • API resend+api://API_KEY@default
Scaleway
  • SMTP scaleway+smtp://PROJECT_ID:API_KEY@default
  • HTTP n/a
  • API scaleway+api://PROJECT_ID:API_KEY@default
Sendgrid
  • SMTP sendgrid+smtp://KEY@default
  • HTTP n/a
  • API sendgrid+api://KEY@default

Caution

Якщо ваші дані безпеки містять спеціальні символи, ви маєте URL-закодувати їх. Наприклад, DSN ses+smtp://ABC1234:abc+12/345@default має бути сконфігурована як ses+smtp://ABC1234:abc%2B12%2F345@default

Caution

Якщо ви хочете використати транспорт ses+smtp разом з Messenger для фонового відправлення повідомлень , вам треба додати параметр ping_threshold до вашого= MAILER_DSN зі значенням меншим, ніж 10: ses+smtp://USERNAME:PASSWORD@default?ping_threshold=9

Caution

Якщо ви надсилаєте користувацькі заголовки при використанні транспорту Amazon SES (щоб отримати їх пізніше через веб-хук), переконайтеся, що ви використовуєте постачальника ses+https, оскільки тільки він їх підтримує.

Note

При використанні SMTP, тайм-аут за замовчуванням для відправлення повідомлення до виклику виключення - це значення, визначене в опції PHP.ini default_socket_timeout.

Note

Окрім SMTP, багато сторонніх транспортів пропонують веб-API для надсилання електронних листів. Для цього вам потрібно встановити (додатково до мосту) компонент HttpClient через composer require symfony/http-client.

Note

Щоб користуватися Google Gmail, ви повинні мати обліковий запис Google з увімкненою 2-етапною верифікацією (2FA) і ви повинні використовувати App Password для аутентифікації. Також зауважте, що Google відкликає ваші App Password, коли ви змінюєте пароль до свого облікового запису Google, і тоді вам потрібно згенерувати новий. Використання інших методів (наприклад, XOAUTH2 або Gmail API) наразі не підтримується. Ви повинні використовувати Gmail тільки в тестових цілях і використовувати реального провайдера у виробництві.

Tip

Якщо ви хочете перевизначити хост провайдера за замовчуванням (щоб налагодити проблему, використовуючи сервіс на кшлталт requestbin.com), змініть default у вашому хості:

1
2
# .env
MAILER_DSN=mailgun+https://KEY:DOMAIN@requestbin.com

Відмітьте, що протокол завжди буде HTTPs, і не може бути змінений.

Note

Певні види транспорту, наприклад, mailgun+smtp, призначено для роботи без ручної конфігурації. Зміна порту шляхом додавання його до вашого DSN не підтримується для жодного з цих транспортів <provider>+smtp. Якщо вам потрібно змінити порт, скористайтеся транспортом smtp, наприклад, так:

1
2
# .env
MAILER_DSN=smtp://KEY:DOMAIN@smtp.eu.mailgun.org.com:25

Tip

Деякі сторонні поштові сервіси при використанні API підтримують зворотні виклики статусу за допомогою веб-хуків. Дивіться Документацію про веб-хуки для отримання додаткової інформації.

Висока доступність

Mailer Symfony підтримує високу доступність через техніку під назвою "відмовостійкість", щоб гарантувати, що листи будуть відправлені, навіть якщо один сервер зазнає невдачі.

Транспорт failover сконфігуровано з двома або більше транспортами та ключовим словом failover:

1
MAILER_DSN="failover(postmark+api://ID@default sendgrid+smtp://KEY@default)"

Транспорт відмовостійкості починає з використання першого транспорту, і якщо він зазнає невдачі, він повторно пробує відправлення з наступним транспортом, поки один з них не призведе до успіху (або поки вони всі не зазнають невдачі).

Балансування навантаження

Mailer Symfony підтримує балансування навантаження через техніку під назвою "round-robin" для розподілу робочого навантаження відправлення листів на декілька транспортів.

Транспорт round-robin сконфігуровано з двома або більше транспортами та ключовим словом roundrobin:

1
MAILER_DSN="roundrobin(postmark+api://ID@default sendgrid+smtp://KEY@default)"

Транспорт round-robin починає з рандомно обраного транспорту, а потім переключається на наступний доступний трансфер для кожного наступного листа.

Як і з транспортом failover, round-robin повторно намагається зробити відправлення, поки транспорт не досягне успіху (або поки всі не зазнають невдачі). На відміну від транспорту failover, він розповсюджує навантаження по всім своїм транспортам.

Верифікація точок TLS

За замовчуванням, транспорт SMTP виконує верифікацію точок TLS. Ця поведінка конфігурується опцією verify_peer. Хоча і не рекомендується відключати верифікацію з міркувань безпеки, це може бути корисним при розробці додатку або при використанні самозавіреного сертифікату:

1
$dsn = 'smtp://user:pass@smtp.example.com?verify_peer=0';

Верифікація відбитку пальця TLS Peer

Додаткову верифікацію відбитків пальців можна ввімкнути за допомогою опції peer_fingerprint. Це особливо корисно, коли використовується самопідписаний сертифікат і вимкнення verify_peer є необхідним, але безпека все одно бажана. Відбиток пальця можна вказати у вигляді хешу SHA1 або MD:

1
$dsn = 'smtp://user:pass@smtp.example.com?peer_fingerprint=6A1CF3B08D175A284C30BC10DE19162307C7286E';

Disabling Automatic TLS

7.1

Опція для відключення автоматичного TLS була представлена в Symfony 7.1.

За замовчуванням компонент Mailer використовуватиме шифрування, якщо розширення OpenSSL увімкнено, а SMTP-сервер підтримує STARTTLS.Таку поведінку можна вимкнути за допомогою виклику setAutoTls(false) в екземплярі EsmtpTransport або встановлення опції auto_tls у значення false у DSN:

1
$dsn = 'smtp://user:pass@10.0.0.25?auto_tls=false';

Caution

Не рекомендується вимикати TLS під час підключення до SMTP-сервера через Інтернет, але це може бути корисно, коли і додаток, і сервер SMTP знаходяться в захищеній мережі, де немає необхідності в додатковому шифруванні.

Note

Це налаштування працює тільки за умови використання протоколу smtp://.

Перевизначення аутентифікаторів SMTP за замовчуванням

За замовчуванням, SMTP-транспорт намагатиметься увійти в систему, використовуючи всі методи аутентифікації, доступні на SMTP-сервері, один за одним. У деяких випадках може бути корисно перевизначити підтримувані методи аутентифікації, щоб гарантувати, що бажаний метод буде використано в першу чергу.

Це можна зробити за допомогою конструктора EsmtpTransport або за допомогою методу setAuthenticators():

1
2
3
4
5
6
7
8
9
10
11
12
13
use Symfony\Component\Mailer\Transport\Smtp\Auth\XOAuth2Authenticator;
use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport;

// Обрати одну з цих 2 опцій:

// Опція 1: передати аутентифікатори конструктору
$transport = new EsmtpTransport(
    host: 'oauth-smtp.domain.tld',
    authenticators: [new XOAuth2Authenticator()]
);

// Опція 2: викликати метод для перевизначення аутентифікаторів
$transport->setAuthenticators([new XOAuth2Authenticator()]);

Інші опції

command

Команда для виконання транспортом sendmail:

1
$dsn = 'sendmail://default?command=/usr/sbin/sendmail%20-oi%20-t'
local_domain

Ім'я домену для використання в команді HELO:

1
$dsn = 'smtps://smtp.example.com?local_domain=example.org'
restart_threshold

Максимальна кількість повідомлень для відправки до перезавантаження транспорту. Може бути використана разом з restart_threshold_sleep:

1
$dsn = 'smtps://smtp.example.com?restart_threshold=10&restart_threshold_sleep=1'
restart_threshold_sleep

Кількість секунд сну між зупинкою та перезавантаженням транспорту. Часто поєднується з restart_threshold:

1
$dsn = 'smtps://smtp.example.com?restart_threshold=10&restart_threshold_sleep=1'
ping_threshold

Мінімальна кількість секунд між двома повідомленнями, необхідна для пінгу серверу:

1
$dsn = 'smtps://smtp.example.com?ping_threshold=200'
max_per_second
Кількість повідомлень, які відправляються за секунду (0, щоб відключити це обмеження)

number of messages to send per second (0 to disable this limitation):

1
$dsn = 'smtps://smtp.example.com?max_per_second=2'

Користувацькі фабрики транспорту

Якщо ви хочете підтримувати свою власну DSN (acme://...), ви можете створити користувацьку фабрику транспорту. Для цього створіть клас, який реалізує TransportFactoryInterface або, якщо ви бажаєте, розширте клас AbstractTransportFactory, щоб зберегти частину шаблонного коду:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/Mailer/AcmeTransportFactory.php
final class AcmeTransportFactory extends AbstractTransportFactory
{
    public function create(Dsn $dsn): TransportInterface
    {
        // проаналізувати задану DSN, вилучити дані/повноваження з неї,
        // а потім створити та повернути транспорт
    }

    protected function getSupportedSchemes(): array
    {
        // це підтримує DSN, починаючи з `acme://`
        return ['acme'];
    }
}

Після створення користувацького класу транспорту, зареєструйте його як сервіс у вашому додатку і позначте його тегом mailer.transport_factory.

Створення та відправлення повідомлень

Для відправлення листа, отримайте екземпляр Mailer, використовуючи підказку MailerInterface, та створіть об'єкт Email:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// src/Controller/MailerController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
use Symfony\Component\Routing\Attribute\Route;

class MailerController extends AbstractController
{
    #[Route('/email')]
    public function sendEmail(MailerInterface $mailer): Response
    {
        $email = (new Email())
            ->from('hello@example.com')
            ->to('you@example.com')
            //->cc('cc@example.com')
            //->bcc('bcc@example.com')
            //->replyTo('fabien@example.com')
            //->priority(Email::PRIORITY_HIGH)
            ->subject('Time for Symfony Mailer!')
            ->text('Sending emails is fun again!')
            ->html('<p>See Twig integration for better HTML integration!</p>');

        $mailer->send($email);

        // ...
    }
}

Це все! Повідомлення буде негайно надіслано за допомогою налаштованого вами транспорту. Якщо ви бажаєте надсилати листи асинхронно для підвищення продуктивності, прочитайте розділ Відправлення повідомлень асинхронно . Також, якщо у вашому додатку встановлено компонент Messenger, всі листи за замовчуванням будуть надсилатися асинхронно (але ви можете змінити це ).

Адреси електронної пошти

Всі методи, які потребують адрес електронної пошти (from(), to(), та ін.), приймають як рядки, так і об'єкти адрес:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ...
use Symfony\Component\Mime\Address;

$email = (new Email())
    // адреса пошти як звичайний рядок
    ->from('fabien@example.com')

    // адреса пошти як об'єкт
    ->from(new Address('fabien@example.com'))

    // визначення адреси та імені пошти як об'єкту
    // (поштові клієнти відобразять ім'я)
    ->from(new Address('fabien@example.com', 'Fabien'))

    // визначення адреси та імені пошти як рядку
    // (формат має відповідати: 'Ім'я <email@example.com>')
    ->from(Address::create('Fabien Potencier <fabien@example.com>'))

    // ...
;

Tip

Замість виклику ->from() кожний раз при створенні листа, ви можете сконфігурувати листи глобально , щоб встановити одну й ту саму From листа у всіх повідомленнях.

Note

Локальная частина адреси (те, що перед @) може включати в себе символи UTF-8, окрім адреси відправника (щоб уникнути помилок з поверненими листами). Наприклад: föóbàr@example.com, 用户@example.com, θσερ@example.com, і т.д.

Використовуйте методи addTo(), addCc(), або addBcc(), щоб додати більше адрес:

1
2
3
4
5
6
7
8
$email = (new Email())
    ->to('foo@example.com')
    ->addTo('bar@example.com')
    ->cc('cc@example.com')
    ->addCc('cc2@example.com')

    // ...
;

Як варіант, ви можете передати декілька адрес кожному методу:

1
2
3
4
5
6
7
8
$toAddresses = ['foo@example.com', new Address('bar@example.com')];

$email = (new Email())
    ->to(...$toAddresses)
    ->cc('cc1@example.com', 'cc2@example.com')

    // ...
;

Заголовки повідомлень

Повідомлення містять деяку кількість полів заголовків, щоб описати їх зміст. Symfony встановлює всі заголовки автоматично, але ви також можете встановити власні заголовки. Існують різні типи заголовків (заголовок Id, заголовок Mailbox, заголовок Date і т.д.), але у більшості випадків ви будете встановлювати текстові заголовки:

1
2
3
4
5
6
7
8
9
10
11
12
$email = (new Email())
    ->getHeaders()
        // цей заголовок повідомляє авто-відповідачам ("режим відпустки електронної пошти")
        // не відповідати на це повідомлення, так як це автоматизований лист
        ->addTextHeader('X-Auto-Response-Suppress', 'OOF, DR, RN, NRN, AutoReply');

        // використати масив, якщо ви хочете додати заголовок з багатьма значеннями
        // (for example in the "References" or "In-Reply-To" header)
        ->addIdHeader('References', ['123@example.com', '456@example.com'])

    // ...
;

Tip

Замість виклику ->addTextHeader() кожний раз при створенні листа, ви можете сконфігурувати листи глобально , щоб встановити одні й ті самі заголовки у всіх відправлених листах.

Зміст повідомлення

Текстовий та HTML-зміст повідомлень може бути рядками (зазвичай у результаті відображення якогось шаблону) або PHP-джерелами:

1
2
3
4
5
6
7
8
9
10
$email = (new Email())
    // ...
    // простий зміст, визначений як рядок
    ->text('Lorem ipsum...')
    ->html('<p>Lorem ipsum...</p>')

    // прикріпити потік файлів
    ->text(fopen('/path/to/emails/user_signup.txt', 'r'))
    ->html(fopen('/path/to/emails/user_signup.html', 'r'))
;

Tip

Ви також можете використовувати шаблони Twig, щоб відобразити зміст тексту та HTML. Прочитайте розділ Twig: HTML & CSS далі в цій статті, щоб дізнатися більше.

Прикріплення файлів

Використайте метод addPart() з BodyFile, щоб додати файли, які існують у вашій файловій системі:

1
2
3
4
5
6
7
8
9
10
11
12
use Symfony\Component\Mime\Part\DataPart;
use Symfony\Component\Mime\Part\File;
// ...

$email = (new Email())
    // ...
    ->addPart(new DataPart(new File('/path/to/documents/terms-of-use.pdf')))
    // опціонально ви можете вказати email-клієнтам відображати користувацьке імʼя для цього файлу
    ->addPart(new DataPart(new File('/path/to/documents/privacy.pdf'), 'Privacy Policy'))
    // опціонально ви можете надати чіткий MIME-тип (інакше його не буде вгадано)
    ->addPart(new DataPart(new File('/path/to/documents/contract.doc'), 'Contract', 'application/msword'))
;

Як варіант, ви можете прикріпляти зміст з потоку шляхом передачі його напряму DataPart :

1
2
3
4
$email = (new Email())
    // ...
    ->addPart(new DataPart(fopen('/path/to/documents/contract.doc', 'r')))
;

Вбудовування зображень

Якщо ви хочете відобразити зображення всередині вашого листа, ви маєте їх вбудувати, а не додавати як вкладення. При використанні Twig для відображення змісту листа, як пояснюється далі в цій статті , зображення вбудовуються автоматично. В іншому випадку, вам потрібно буде вбудувати їх вручну.

Спочатку, використайте метод addPart(), щоб додати зображення з файлу або потоку:

1
2
3
4
5
6
7
$email = (new Email())
    // ...
    // отримати зміст збораження з PHP-джерела
    ->addPart((new DataPart(fopen('/path/to/images/logo.png', 'r'), 'logo', 'image/png'))->asInline())
    // отримати зміст зображення з існуючого файлу
    ->addPart((new DataPart(new File('/path/to/images/signature.gif'), 'footer-signature', 'image/gif'))->asInline())
;

Використайте метод asInline(), щоб вбудувати зміст замість того, щоб прикріплювати його.

Другий необов'язковий аргумент обох методів - це ім'я зображення ("Content-ID" за стандартом MIME). Його значення - це довільний рядок, який використовується для того, щоб посилатися на зображення всередині HTML змісту:

1
2
3
4
5
6
7
8
9
10
11
$email = (new Email())
    // ...
    ->addPart((new DataPart(fopen('/path/to/images/logo.png', 'r'), 'logo', 'image/png'))->asInline())
    ->addPart((new DataPart(new File('/path/to/images/signature.gif'), 'footer-signature', 'image/gif'))->asInline())

    // посилайтеся на зображення, використовуючи синтаксис 'cid:' + "image embed name"
    ->html('<img src="cid:logo"> ... <img src="cid:footer-signature"> ...')

    // використати такий самий синтаксис для зображень, доданих як фонові зображення HTML
    ->html('... <div background="cid:footer-signature"> ... </div> ...')
;
Ви також можете використовувати метод DataPart::setContentId()

для визначення користувацького Content-ID для зображення і використання його як посилання на cid:

1
2
3
4
5
6
7
8
$part = new DataPart(new File('/path/to/images/signature.gif'));
$part->setContentId('footer-signature');

$email = (new Email())
    // ...
    ->addPart($part->asInline())
    ->html('... <img src="cid:footer-signature"> ...')
;

Конфігурація листів глобально

Замість виклику ->from() по кожному листу, який ви створюєте, ви можете сокнфігурувати це значення глобально, щоб воно було встановлене у всіх відправлених листах. Те ж саме вірно і для ->to(), і для заголовків.

1
2
3
4
5
6
7
8
9
10
# config/packages/mailer.yaml
framework:
    mailer:
        envelope:
            sender: 'fabien@example.com'
            recipients: ['foo@example.com', 'bar@example.com']
        headers:
            From: 'Fabien <fabien@example.com>'
            Bcc: 'baz@example.com'
            X-Custom-Header: 'foobar'

Caution

Деякі сторонні постачальники не підтримують використання ключових слів типу from у headers. Перегляньте документацію вашого провайдера до встановлення будь-якого глобального заголовку.

Обробка помилок відправлення

Symfony Mailer вважає відправку успішною, коли ваш транспорт (SMTP-сервер або сторонній постачальник) приймає лист для подальшої доставки. Повідомлення може бути втрачене або не доставлене пізніше через проблеми у вашому постачальнику, але це виходить за межі можливостей вашого додатку Symfony.

Якщо при передачі листа вашому транспорту сталася помилка, Symfony викликає TransportExceptionInterface. Виявіть виключення, щоб відновитися після помилки або для відображення якогось повідомлення:

1
2
3
4
5
6
7
8
9
10
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;

$email = new Email();
// ...
try {
    $mailer->send($email);
} catch (TransportExceptionInterface $e) {
    // якась помилка завадила відправленню листа; відобразити повідомлення
    // про помилку або спробувати відправити повідомлення знову
}

Налагодження листів

Об'єкт SentMessage, повернений методом send() класу TransportInterface, надає доступ до початкового повідомлення (getOriginalMessage()) і до деякої інформації налагодження (getDebug()) на кшталт HTTP-викликів, зроблених HTTP-транспортом, що корисно для налагодження помилок.

Note

Якщо у вашому коді використовувався MailerInterface, вам потрібно замінити його на TransportInterface, щоб отримати об'єкт SentMessage.

Note

Деякі постачальники поштових програм змінюють Message-Id при відправленні листа. Метод getMessageId() з SentMessage завжди повертає визначений ID повідомлення (той самий рандомний ID, згенерований Symfony, або новий ID, згенерований постачальником поштової програми).

Виключення, пов'язані з транспортом поштової програми (які реалізують TransportException) також надають цю інформацію налагодження через метод getDebug().

Twig: HTML і CSS

Компонент Mime інтегрується з шаблонізатором Twig , щоб надати просунуті функції на кшталт вбудовування CSS-стилів та підтримки для фреймворків HTML/CSS, щоб створювати складні повідомлення HTML-листів. Спочатку переконайтеся, що Twig встановлено:

1
2
3
4
$ composer require symfony/twig-bundle

# або, якщо ви використовуєте компонент у додатку не на Symfony:
# composer require symfony/twig-bridge

HTML-зміст

Щоб визначити зміст вашого листа з Twig, використайте клас TemplatedEmail. Цей клас розширює нормальний клас Email, але додає деякі нові методі для шаблонів Twig:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use Symfony\Bridge\Twig\Mime\TemplatedEmail;

$email = (new TemplatedEmail())
    ->from('fabien@example.com')
    ->to(new Address('ryan@example.com'))
    ->subject('Thanks for signing up!')

    // шлях шаблону Twig для відображення
    ->htmlTemplate('emails/signup.html.twig')

    // передайте змінні (ім'я => значення) шаблону
    ->context([
        'expiration_date' => new \DateTime('+7 days'),
        'username' => 'foo',
    ])
;

Потім створіть шаблон:

1
2
3
4
5
6
7
8
9
10
11
12
{# templates/emails/signup.html.twig #}
<h1>Welcome {{ email.toName }}!</h1>

<p>
    You signed up as {{ username }} the following email:
</p>
<p><code>{{ email.to[0].address }}</code></p>

<p>
    <a href="#">Click here to activate your account</a>
    (this link is valid until {{ expiration_date|date('F jS') }})
</p>

Шаблон Twig має доступ до будь-яких параметрів, які передано в метод context() класу TemplatedEmail, а також спеціальної змінної під назвою email, яка є екземпляром WrappedTemplatedEmail.

Текстовий зміст

Коли текстовий зміст TemplatedEmail не визначено чітко, поштова програма автоматично згенерує його, перетворивши HTML-зміст у текст.

Symfony використовує наступну стратегію коли генерує текстову версію листа:

  • Якщо було сконфігуровано чіткий перетворювач з HTML на текст (див. twig.mailer.html_to_text_converter ), вона викликає його;
  • Якщо ні, і у вас встановлено league/html-to-markdown у вашому додатку, вона використовує його задля перетворення HTML на розмітку (щоб текстовий електронний лист був візуально привабливим);
  • В інших випадках, вона застосовує PHP-функцію strip_tags до оригінального змісту HTML.

Якщо ви хочете визначити текстовий зміст самостійно, використайте метод text(), пояснений у попередніх розділах, або метод textTemplate(), наданий класом TemplatedEmail:

1
2
3
4
5
6
7
8
9
+ use Symfony\Bridge\Twig\Mime\TemplatedEmail;

$email = (new TemplatedEmail())
    // ...

    ->htmlTemplate('emails/signup.html.twig')
+     ->textTemplate('emails/signup.txt.twig')
    // ...
;

Вбудовування зображень

Замість того, щоб розбиратися з синтаксисом <img src="cid: ...">, який пояснювався у попердніх розділах, при використанні Twig для відображення змісту листа, ви можете послатися на файли зображень, як звичайно. Для початку, щоб все спростити, визначіть простір іменґ Twig під назвою images, який вказує на той каталог, де зберігаються ваші зображення:

1
2
3
4
5
6
7
# config/packages/twig.yaml
twig:
    # ...

    paths:
        # вкажіть туди, де живуть ваші зображення
        '%kernel.project_dir%/assets/images': images

Тепер використайте спеціального помічника Twig email.image(), щоб вбудувати зображення у зміст листа:

1
2
3
4
5
{# '@images/' відноситься до простору імен Twig, визначеного раніше #}
<img src="{{ email.image('@images/logo.png') }}" alt="Logo">

<h1>Welcome {{ email.toName }}!</h1>
{# ... #}

Вбудовування CSS-стилів

Дизайн HTML-змісту листа дуже відрізняється від дизайну звичайної HTML-сторінки. Перш за все, більшість поштових клієнтів підтримують лише частину всіх CSS-функцій. Більш того, популярні поштові клієнти, на кшталт Gmail, не підтримують визначальні стилі всередині розділів <style> ... </style>, і ви маєте вбудувати всі CSS-стилі.

Вбудовування CSS означає, що кожний HTML-тег має визначати атрибут style, з усіма його CSS-стилями. Це може сильно заплутати впорядкування вашого CSS. Тому Twig надає CssInlinerExtension, який автоматизує все для вас. Встановіть його:

1
$ composer require twig/extra-bundle twig/cssinliner-extra

Розширення вмикається автоматично. Щоб використати його, огорніть весь шаблон у фільтр inline_css:

1
2
3
4
5
6
7
8
9
10
11
{% apply inline_css %}
    <style>
        {# тут визначте ваші СSS-стилі, як звичайно #}
        h1 {
            color: #333;
        }
    </style>

    <h1>Welcome {{ email.toName }}!</h1>
    {# ... #}
{% endapply %}

Використання зовнішніх СSS-файлів

Ви також можете визначати СSS-стилі у зовнішніх файлах та передавати їх як аргументи фільтру:

1
2
3
4
{% apply inline_css(source('@styles/email.css')) %}
    <h1>Welcome {{ username }}!</h1>
    {# ... #}
{% endapply %}

Ви можете передати необмежену кількість аргументів до inline_css() для завантаження декількох CSS-файлів. Для того, щоб цей приклад працював, вам також треба визначити новий простів імен Twig під назвою styles, який вказує на каталог, де живе email.css:

1
2
3
4
5
6
7
# config/packages/twig.yaml
twig:
    # ...

    paths:
        # вкажіть туди, де живуть ваші css-файли
        '%kernel.project_dir%/assets/styles': styles

Відображення змісту з розміткою

Twig надає інше розширення під назвою MarkdownExtension, яке дозволяє вам визначати зміст листів з використанням синтаксису Markdown. Щоб використати його, встановіть розширення та бібліотеку конверсій Markdown (розширення сумісне з декількома популярними бібліотеками):

1
2
# замість league/commonmark, ви також можете використати erusev/parsedown або michelf/php-markdown
$ composer require twig/extra-bundle twig/markdown-extra league/commonmark

Розширення додає фільтр markdown_to_html, який ви можете використати, щоб перетворити частини або весь змісти листа з Markdown в HTML:

1
2
3
4
5
6
7
8
9
{% apply markdown_to_html %}
    Welcome {{ email.toName }}!
    ===========================

    Ви підписалися на наш сайт, використовуючи наступну адресу електронної пошти:
    `{{ email.to[0].address }}`

    [Натисніть тут, щоб активувати ваш акаунт]({{ url('...') }})
{% endapply %}

Мова шаблонізації листів Inky

Створення листів з чудовим дизайном, які працюють у кожному поштовому клієнті, настільки складне, що існують цілі фреймворки HTML/CSS, присвячені цьому. Один з найпопулярніших фреймворків називається Inky. Він визначає синтаксис, засновуючись на тегах типу HTML, які пізніше перетворюються у справжній HTML-код та відправляються користувачас:

1
2
3
4
5
6
<!-- спрощений приклад синтаксису Inky -->
<container>
    <row>
        <columns>This is a column.</columns>
    </row>
</container>

Twig надає інтеграцію з Inky через InkyExtension. Спочатку, встановіть розширення у вашому додатку:

1
$ composer require twig/extra-bundle twig/inky-extra

Розширення додає фільтр inky_to_html, який може бути використаний для перетворення частин або всього змісту листа з Inky в HTML:

1
2
3
4
5
6
7
8
9
10
11
12
{% apply inky_to_html %}
    <container>
        <row class="header">
            <columns>
                <spacer size="16"></spacer>
                <h1 class="text-center">Welcome {{ email.toName }}!</h1>
            </columns>

            {# ... #}
        </row>
    </container>
{% endapply %}

Ви можете скомбінувати всі фільтри, щоб створювати складні повідомлення листів:

1
2
3
{% apply inky_to_html|inline_css(source('@styles/foundation-emails.css')) %}
    {# ... #}
{% endapply %}

Це використовує простір імен стилів Twig , який ми створили раніше. Ви могли б, наприклад, завантажити файл foundation-emails.css прямо з GitHub і зберегти його в assets/styles.

Цифровий підпис та кодування повідомлень

Існує можливість цифрового підписання та/або кодування повідомлень електронних листів для посилення їхньої цілісності/безпеки. Обидві опції можна об'єднати для кодування повідомлення з електронним підписом та/або цифрофого підписання закодованого повідомлення.

Перед тим, як підписувати/закодовувати повідомлення, переконайтеся, що у вас є:

Tip

При використанні OpenSSL для генерування сертифікатів, не забудьте додати опцію команди -addtrust emailProtection.

Caution

Підписання та шифрування повідомлень вимагає повного відображення їх змісту. Наприклад, зміст шаблонізованих листів обробляється за допомогою MessageListener. Отже, якщо ви хочете підписати та/або зашифрувати таке повідомлення, вам потрібно зробити це в слухачі MessageEvent, запущеному після нього (вам потрібно встановити від'ємний пріоритет для вашого слухача).

Цифровий підпис повідомлень

При додаванні цифрового підпису до повідомлення, генерується криптографічний хеш для всього змісту повідомелння (включно зі вкладеннями). Цей хеш додається як вкладення, щоб отримувач міг валідувати цілісність отриманого повідомлення. Однак, зміст початкового повідомлення залишається читаним для поштових агентів, які не підтримують підписані повідомлення, тому ви також маєте закодувати повідомлення, якщо ви хочете приховати його зміст.

Ви можете підписувати повідомлення використовуючи S/MIME або DKIM. В обох випадках, сертифікат та приватний ключ мають бути PEM-закодовані, і можут бути або створені з використанням, наприклад, OpenSSL, або отримані в офіційной Сертифікаційної компанії (CA). Отримувач листа повинен мати сертифікат CA у списку довірених осіб, щоб верифікувати підпис.

Caution

Якщо ви використовуєте підпис повідомлення, надсилання до Bcc буде вилучено з повідомлення. Якщо вам потрібно надіслати повідомлення кільком одержувачам, вам потрібно обчислити новий підпис для кожного одержувача.

Підпис S/MIME

S/MIME стандартна для кодування публічних ключів та підписання даних MIME. Вона вимагає використання як сертифікату, так і приватного ключа:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use Symfony\Component\Mime\Crypto\SMimeSigner;
use Symfony\Component\Mime\Email;

$email = (new Email())
    ->from('hello@example.com')
    // ...
    ->html('...');

$signer = new SMimeSigner('/path/to/certificate.crt', '/path/to/certificate-private-key.key');
// якщо приватний ключ має кодову фразу, передайте її як третій аргумент
// new SMimeSigner('/path/to/certificate.crt', '/path/to/certificate-private-key.key', 'the-passphrase');

$signedEmail = $signer->sign($email);
// тепер використайте компонент Mailer, щоб відправити цей $signedEmail замість початкового листа

Tip

Клас SMimeSigner визначає інші необов'язкові аргументи для передачі проміжкових сертифікатів та для конфігурації процесу підписання, використовуючи побітні опції оператору для PHP-функції openssl_pkcs7_sign.

Підпис DKIM

DKIM - це метод аутентифікації листа, який додає цифровий підпис, що посилається на основний домен, до кожного вихідного листа. Він вимагає приватний ключ, але не сертифікат:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use Symfony\Component\Mime\Crypto\DkimSigner;
use Symfony\Component\Mime\Email;

$email = (new Email())
    ->from('hello@example.com')
    // ...
    ->html('...');

// перший аргумент: такий же, як openssl_pkey_get_private(), або рядок зі змістом
// приватного ключа, або абсолютний шлях до нього (з префіксом 'file://')
// другий і третій аргументи: ім'я домену та "селектор", використовувания для виконання пошуку DNS
// (селектор - це рядок, використовуваний для вказання на конкретний запис публічного ключа DKIM у вашій DNS)
$signer = new DkimSigner('file:///path/to/private-key.key', 'example.com', 'sf');
// якщо приватний ключ має кодову фразу, передайте її як п'ятий аргумент
// new DkimSigner('file:///path/to/private-key.key', 'example.com', 'sf', [], 'the-passphrase');

$signedEmail = $signer->sign($email);
// тепер використайте компонент Mailer, щоб відправити цей $signedEmail замість початкового листа

// підпис DKIM надає багато опцій конфігурації та об'єкт помічника для їхнього конфігурування
use Symfony\Component\Mime\Crypto\DkimOptions;

$signedEmail = $signer->sign($email, (new DkimOptions())
    ->bodyCanon('relaxed')
    ->headerCanon('relaxed')
    ->headersToIgnore(['Message-ID'])
    ->toArray()
);

Кодування повідомлень

При кодуванні повідомлення, все повідомлення (включно із вкладеннями) кодується з використанням сертифікату. Отже, тільки отримувачі, які мають відповідний приватний ключ, можуть прочитати зміст початкового повідомлення:

1
2
3
4
5
6
7
8
9
10
11
use Symfony\Component\Mime\Crypto\SMimeEncrypter;
use Symfony\Component\Mime\Email;

$email = (new Email())
    ->from('hello@example.com')
    // ...
    ->html('...');

$encrypter = new SMimeEncrypter('/path/to/certificate.crt');
$encryptedEmail = $encrypter->encrypt($email);
// тепер використайте компонент Mailer, щоб відправити цей $encryptedEmail замість початкового листа

Ви можете передати більше одного сертифікату конструктору SMimeEncrypter і він обере відповідний сертифікат, в залежності від опції To:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$firstEmail = (new Email())
    // ...
    ->to('jane@example.com');

$secondEmail = (new Email())
    // ...
    ->to('john@example.com');

// другий необов'язковий аргумент SMimeEncrypter визначає, який алгоритм кодування використовується
// (має бути однією з цих констант: https://www.php.net/manual/en/openssl.ciphers.php)
$encrypter = new SMimeEncrypter([
    // ключ = отримувач листа; значення = шлях до файлу сертифікату
    'jane@example.com' => '/path/to/first-certificate.crt',
    'john@example.com' => '/path/to/second-certificate.crt',
]);

$firstEncryptedEmail = $encrypter->encrypt($firstEmail);
$secondEncryptedEmail = $encrypter->encrypt($secondEmail);

Декілька транспортів листів

Ви можете захотіти використати більше одного транспорту поштової програми для доставки ваших повідомлень. Це можна сконфігурувати, замінивши запис конфігурації dsn на запис transports, наступним чином:

1
2
3
4
5
6
# config/packages/mailer.yaml
framework:
    mailer:
        transports:
            main: '%env(MAILER_DSN)%'
            alternative: '%env(MAILER_DSN_IMPORTANT)%'

За замовчуванням використовується перший транспорт. Інші транспорти можуть бути використані шляхом додавання до листа текстового заголовку X-Transport:

1
2
3
4
5
6
// Відправити, використовуючи перший "головний" транспорт ...
$mailer->send($email);

// ... або використати "альтернативний"
$email->getHeaders()->addTextHeader('X-Transport', 'alternative');
$mailer->send($email);

Асинхронне відправлення повідомлень

Коли ви викликаєте $mailer->send($email), лист одразу ж відправляється транспорту. Для покращення продуктивності, ви можете використати переваги Месенджеру, щоб відправляти повідомлення пізніше через транспорт Месенджера.

Почніть, слідуючи документації Месенджеру та сконфігурувавши транспорт. Коли все буде налаштовано, при виклику $mailer->send(), повідомлення SendEmailMessage буде запущене через автобус повідомлень за замовчуванням (messenger.default_bus). Якщо припустити, що у вас є транспорт під назвою async, ви можете маршрутизувати повідомлення у нього:

1
2
3
4
5
6
7
8
# config/packages/messenger.yaml
framework:
    messenger:
        transports:
            async: "%env(MESSENGER_TRANSPORT_DSN)%"

        routing:
            'Symfony\Component\Mailer\Messenger\SendEmailMessage': async

Завдяки цьому, замість негайної доставки, повідомлення будуть відправлені транспорту для обробки пізніше (див. ). Відмітьте, що "відображення" листа (обчислені заголовки, відображення тіла, ...) також відкладено і відбудеться прямо перед відпарвкою листа обробником Messenger.

При відправленні листа асинхронно, його езкемпляр повинен бути серіалізовуваним. Це завжди так для екземплярів Email, але при відправці TemplatedEmail, ви повинні гарантувати, що context серіалізовуваний. Якщо у вас є несеріалізовувані змінні, типу сутностей Doctrine, або замініть їх на більш конкретні змінні, або відобразіть лист перед викликом $mailer->send($email):

1
2
3
4
5
6
7
8
9
10
11
12
13
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\BodyRendererInterface;

public function action(MailerInterface $mailer, BodyRendererInterface $bodyRenderer): void
{
    $email = (new TemplatedEmail())
        ->htmlTemplate($template)
        ->context($context)
    ;
    $bodyRenderer->render($email);

    $mailer->send($email);
}

Ви можете сконфігурувати, який автобус використовується для запуску повідомлення, використовуючи опцію message_bus. Ви також можете встановити її як false, щоб викликати транспорт Mailer напряму та відключити асинхронну доставку.

1
2
3
4
# config/packages/mailer.yaml
framework:
    mailer:
        message_bus: app.another_bus

Note

У випадку довготривалих скриптів і коли Mailer використовує SmtpTransport, ви можете відключитися від SMTP-сервера, щоб уникнути відкритого зʼєднання з SMTP-сервером між відправкою листів. Ви можете зробити це, використавши метод stop().

Ви також можете обрати транспоррт, додавши заголовок X-Bus-Transport (який буде автоматично видалено з фінального повідомлення):

1
2
3
// Використати автобус транспорту "app.another_bus":
$email->getHeaders()->addTextHeader('X-Bus-Transport', 'app.another_bus');
$mailer->send($email);

Додавання тегів та метаданих до листів

Певні сторонні транспорти підтримують теги та метадані листів, що може бути використано для групування, відслідковування і робочих процесів. Ви можете додати їх, використовуючи класи TagHeader та MetadataHeader. Якщо ваш транспорт підтримує заголовки, він перетворить їх у відповідний формат:

1
2
3
4
5
6
use Symfony\Component\Mailer\Header\MetadataHeader;
use Symfony\Component\Mailer\Header\TagHeader;

$email->getHeaders()->add(new TagHeader('password-reset'));
$email->getHeaders()->add(new MetadataHeader('Color', 'blue'));
$email->getHeaders()->add(new MetadataHeader('Client-ID', '12345'));

Якщо ваш транспорт не підтримує теги та метадані, вони будуть додані у вигляді користувацьких заголовків:

1
2
3
X-Tag: password-reset
X-Metadata-Color: blue
X-Metadata-Client-ID: 12345

На даний момент, наступний транспорт підтримує теги та метадані:

  • MailChimp
  • Mailgun
  • Postmark
  • Sendgrid
  • Sendinblue

Наступний транспорт підтримує лише теги:

  • MailPace

Наступний трнаспорт підтримує лише метадані:

  • Amazon SES (відмітьте, що Amazon називає цю функцію "тегами", але Symfony називає це "метаданими" через те, що вони містять ключ та значення)

Чернетки електронних листів

DraftEmail - це сеціальний екземпляр Email. Його ціль полягає в тому, щоб побудувати електронний лист (з тілом, вкладеннями та ін.) та зробити його доступним для завантаження як .eml із заголовком X-Unsent. Багато email-клієнтів можуть відкривати ці файли та взаємодіяти з ними, як з "чернетками електронного листа". Ви можете використати їх, щоб створювати просунуті посилання mailto:.

Here's an example of making one available to download:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// src/Controller/DownloadEmailController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\Mime\DraftEmail;
use Symfony\Component\Routing\Attribute\Route;

class DownloadEmailController extends AbstractController
{
    #[Route('/download-email')]
    public function __invoke(): Response
    {
        $message = (new DraftEmail())
            ->html($this->renderView(/* ... */))
            ->addPart(/* ... */)
        ;

        $response = new Response($message->toString());
        $contentDisposition = $response->headers->makeDisposition(
            ResponseHeaderBag::DISPOSITION_ATTACHMENT,
            'download.eml'
        );
        $response->headers->set('Content-Type', 'message/rfc822');
        $response->headers->set('Content-Disposition', $contentDisposition);

        return $response;
    }
}

Note

Так як DraftEmail можуть бути створені без Кому/Від, вони не можуть бути відправлені без Mailer.

Події Mailer

MessageEvent

Клас події: MessageEvent

MessageEvent дозволяє змінювати повідомлення Mailer та конверт перед відправкою листа:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Mailer\Event\MessageEvent;
use Symfony\Component\Mime\Email;

public function onMessage(MessageEvent $event): void
{
    $message = $event->getMessage();
    if (!$message instanceof Email) {
        return;
    }
    // зробити щось з повідомленням (логування, ...)

    // та/або додати якісь штампи Мессенджеру
    $event->addStamp(new SomeMessengerStamp());
}

Якщо ви хочете запобігти відправці повідомлення, викличте reject() (це також зупинить розповсюдження події):

1
2
3
4
5
6
use Symfony\Component\Mailer\Event\MessageEvent;

public function onMessage(MessageEvent $event): void
{
    $event->reject();
}

Виконайте цю команду, щоб дізнатися, які слухачі зареєстровані для цієї події, та їх пріоритети:

1
$ php bin/console debug:event-dispatcher "Symfony\Component\Mailer\Event\MessageEvent"

SentMessageEvent

Клас події: SentMessageEvent

SentMessageEvent дозволяє вам діяти у класі SentMessage, щоб отримати доступ до початкового повідомлення (getOriginalMessage()) та деякої інформації налагодження (getDebug()), такої як HTTP-виклики, зроблені транспортом HTTP, що корисно для налагодження помилок:

1
2
3
4
5
6
7
8
9
10
11
12
13
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Mailer\Event\SentMessageEvent;
use Symfony\Component\Mailer\SentMessage;

public function onMessage(SentMessageEvent $event): void
{
    $message = $event->getMessage();
    if (!$message instanceof SentMessage) {
        return;
    }

    // зробити щось з повідомленням
}

Виконайте цю команду, щоб дізнатися, які слухачі зареєстровані для цієї події, та їх пріоритети:

1
$ php bin/console debug:event-dispatcher "Symfony\Component\Mailer\Event\SentMessageEvent"

FailedMessageEvent

Клас події: FailedMessageEvent

FailedMessageEvent дозволяє діяти у початковому повідомленні у випадку невдачі:

1
2
3
4
5
6
7
8
9
10
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Mailer\Event\FailedMessageEvent;

public function onMessage(FailedMessageEvent $event): void
{
    // наприклад, ви можете отримати більше інформації про цю помилку при відправці листа
    $event->getError();

    // зробити щось з повідомленням
}

Виконайте цю команду, щоб дізнатися, які слухачі зареєстровані для цієї події, та їх пріоритети:

1
$ php bin/console debug:event-dispatcher "Symfony\Component\Mailer\Event\FailedMessageEvent"

Розробка та налагодження

Підключення Email Catcher

При локальній розробці рекомендовано використовувати email catcher. Якщо ви включили підтримку Docker через рецепти Symfony, email catcher конфігурується автоматично. На додаток, якщо ви використовуєте локальний веб-сервер Symfony, mailer DSN автоматично розкриваєтьсся через бінарну інтеграцію symfony з Docker .

Відправка тестових електронних листів

Symfony надає команду для відправки листів, яка корисна під час розробки, щоб тестувати, чи правильно працює відправка електронних листів:

1
2
3
# єдиний обовʼязковий аргумент - це адреса отримувача
# (перегляньте допомогу команди, щоб дізнатися про її опції)
$ php bin/console mailer:test someone@example.com

Ця команда обходить автобус Messenger, якщо його сконфігуровано, щоб полегшити тестування листів, навіть якщо споживач Messenger не працює.

Відключення доставки

Під час розробки (або тестування), ви можете захотіти відключити доставку повідомлень повністю. Ви можете зробити це, використовуючи null://null як DSN поштової програми, або у ваших файлах конфігурації .env , або у файлі конфігурації поштової програми (наприклад, у середовищах dev або test):

1
2
3
4
5
# config/packages/mailer.yaml
when@dev:
    framework:
        mailer:
            dsn: 'null://null'

Note

Якщо ви використовуєете Месенджер та маршрутизуєте його до транспорту, повідомлення всеодно буде відправлене цьому транспорту.

Постійна відправка листа за однією адресою

Замість повного відключення доставки, ви можете захотіти завжди відправляти листи за однією конкретною адресою, замість справжньої адреси:

1
2
3
4
5
6
# config/packages/mailer.yaml
when@dev:
    framework:
        mailer:
            envelope:
                recipients: ['youremail@example.com']

Використовуйте опцію allowed_recipients, щоб вказати винятки з поведінки, визначеної в опції recipients; дозволяючи листам, спрямованим до цих конкретних одержувачів, зберігати свою початкову адресу:

1
2
3
4
5
6
7
8
9
10
# config/packages/mailer.yaml
when@dev:
    framework:
        mailer:
            envelope:
                recipients: ['youremail@example.com']
                allowed_recipients:
                    - 'internal@example.com'
                    # ви також можете використати регулярний вираз для визначення дозволених отримувачів
                    - 'internal-.*@example.(com|fr)'

З цією конфігурацією всі листи будуть надсилатися на youremail@example.com, окрім тих, що надсилаються на internal@example.com, internal-monitoring@example.fr і т.д., які будуть отримувати листи як зазвичай.

7.1

Опція allowed_recipients була представлена в Symfony 7.1.

Напишіть функціональний тест

Щоб функціонально протестувати відправку електронного листа, і навіть ствердити зміст або заголовки листа, ви можете використати вбудовані ствердження:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// tests/Controller/MailControllerTest.php
namespace App\Tests\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class MailControllerTest extends WebTestCase
{
    public function testMailIsSentAndContentIsOk(): void
    {
        $client = static::createClient();
        $client->request('GET', '/mail/send');
        $this->assertResponseIsSuccessful();

        $this->assertEmailCount(1); // use assertQueuedEmailCount() when using Messenger

        $email = $this->getMailerMessage();

        $this->assertEmailHtmlBodyContains($email, 'Welcome');
        $this->assertEmailTextBodyContains($email, 'Welcome');
    }
}

Tip

Якщо ваш контролер повертає відповідь перенаправлення після відправлення листа, переконайтеся, що ваш клієнт не слідує за перенаправленнями. Ядро перезавантажиться після перенаправлення, і повідомлення буде втрачено з обробника події.