Дата обновления перевода 2021-12-24
Отправка писем с помощью Mailer¶
Установка¶
Компоненты Symfony Mailer и Mime формируют мощную систему для создания и отправки электронных писем - с поддержкой составных сообщений, интеграцией Twig, встраиванием CSS, прикреплением файлов и многим другим. Установите их с помощью:
1 | $ composer require symfony/mailer
|
Настройка транспорта¶
Письма доставляются с помощью “транспорта”. Сразу после установки, вы
можете отправлять письма через SMTP. сконфигурировав DSN в вашем файле
.env
(параметры user
, pass
и port
необязательны):
1 2 | # .env
MAILER_DSN=smtp://user:[email protected]:port
|
- YAML
1 2 3 4
# config/packages/mailer.yaml framework: mailer: dsn: '%env(MAILER_DSN)%'
- XML
1 2 3 4 5 6 7 8 9 10 11 12 13
<!-- config/packages/mailer.xml --> <?xml version="1.0" encoding="UTF-8" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:framework="http://symfony.com/schema/dic/symfony" xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> <framework:config> <framework:mailer dsn="%env(MAILER_DSN)%"/> </framework:config> </container>
- PHP
1 2 3 4 5 6 7 8 9 10
// config/packages/mailer.php use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; return static function (ContainerConfigurator $containerConfigurator): void { $containerConfigurator->extension('framework', [ 'mailer' => [ 'dsn' => '%env(MAILER_DSN)%', ], ]); };
Caution
Если имя пользователя, пароль или хост содержат в URI любой символ, считающийся особенным,
(такой как +
, @
, $
, #
, /
, :
, *
, !
), вы должны зашифровать
их. См.`RFC 3986`_. чтобы увидеть полный список зарезервированных символов или используйте
функцию urlencode
, чтобы зашифровать их.
Caution
Если мы мигрируете со Swiftmailer (и пакета Swiftmailer), имейте в виду, что формат DSN отличается.
Использование встроенного транспорта¶
New in version 5.2: Нативный протокол был представлен в Symfony 5.2.
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 не сконфигурирован. |
Использование стороннего транспорта¶
Instead of using your own SMTP server or sendmail binary, you can send emails via a 3rd party provider. Mailer supports several - install whichever you want:
Сервис | Установка с помощью |
---|---|
Amazon SES | composer require symfony/amazon-mailer |
Gmail | composer require symfony/google-mailer |
MailChimp | composer require symfony/mailchimp-mailer |
Mailgun | composer require symfony/mailgun-mailer |
Mailjet | composer require symfony/mailjet-mailer |
Postmark | composer require symfony/postmark-mailer |
SendGrid | composer require symfony/sendgrid-mailer |
Sendinblue | composer require symfony/sendinblue-mailer |
OhMySMTP | composer require symfony/oh-my-smtp-mailer |
New in version 5.2: Интеграция Sendinblue была представлена в Symfony 5.2.
New in version 5.4: Интеграция OhMySMTP была представлена в Symfony 5.4.
Каждая библиотека содержит рецепт Symfony Flex, который будет
добавлять пример конфигурации в ваш файл .env
. Например, представьте, что вы
хотите использовать SendGrid. Для начала, установите его:
1 | $ composer require symfony/sendgrid-mailer
|
Теперь у вас будет новая строка в вашем файле .env
, которую вы можете раскомментировать:
1 2 | # .env
MAILER_DSN=sendgrid://[email protected]
|
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 для каждого стороннего поставщика:
Поставщик | SMTP | HTTP | API |
---|---|---|---|
Amazon SES | ses+smtp://USERNAME:PASSWORD@default | ses+https://ACCESS_KEY:SECRET_KEY@default | ses+api://ACCESS_KEY:SECRET_KEY@default |
Google Gmail | gmail+smtp://USERNAME:PASSWORD@default | n/a | n/a |
Mailchimp Mandrill | mandrill+smtp://USERNAME:PASSWORD@default | mandrill+https://KEY@default | mandrill+api://KEY@default |
Mailgun | mailgun+smtp://USERNAME:PASSWORD@default | mailgun+https://KEY:DOMAIN@default | mailgun+api://KEY:DOMAIN@default |
Mailjet | mailjet+smtp://ACCESS_KEY:SECRET_KEY@default | n/a | mailjet+api://ACCESS_KEY:SECRET_KEY@default |
Postmark | postmark+smtp://ID@default | n/a | postmark+api://KEY@default |
Sendgrid | sendgrid+smtp://KEY@default | n/a | sendgrid+api://KEY@default |
Sendinblue | sendinblue+smtp://USERNAME:PASSWORD@default | n/a | sendinblue+api://KEY@default |
OhMySMTP | ohmysmtp+smtp://API_TOKEN@default | n/a | ohmysmtp+api://API_TOKEN@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
New in version 5.4: Опция ping_threshold
для ses-smtp
была представлена в Symfony 5.4.
Note
При использовании SMTP, тайм-аут по умолчанию для отправки сообщения до вызова исключения - это значение, определенное в опции PHP.ini default_socket_timeout.
New in version 5.1: Использование default_socket_timeout
в качестве тайм-аута по умолчанию было
представлено в Symfony 5.1.
Tip
Если вы хотите переопределить хост для поставщика по умолчанию (для отладки
проблемы используя сервис вроде requestbin.com
), измените default
в вашем хостеt:
1 2 3 | # .env
MAILER_DSN=mailgun+https://KEY:[email protected]
MAILER_DSN=mailgun+https://KEY:[email protected]:99
|
Заметьте, что протокол всегда будет HTTPs и не может быть изменен.
Высокая доступность¶
Mailer Symfony поддерживает высокую доступность через технику под названием “failover”, чтобы гарантировать, что письма будут отправлены, даже если один сервер потерпит неудачу.
Транспорт failover сконфигурирован с двумя или более транспортами и ключевым
словом failover
:
1 | MAILER_DSN="failover(postmark+api://[email protected] sendgrid+smtp://[email protected])"
|
Транспорт failover начинает с использования первого транспорта, и если он терпит неудачу, он повторно попробует отправку со следующим транспортом, пока один из них не приведет к успеху (или пока они все не потерпят неудачу).
Балансировка нагрузки¶
Mailer Symfony поддерживает балансировку нагрузки через технику под названием “round-robin” для распределения рабочей нагрузки отправки писем по нескольким транспортам.
Транспорт round-robin сконфигурирован с двумя или более транспортами и ключевым
словом roundrobin
:
1 | MAILER_DSN="roundrobin(postmark+api://[email protected] sendgrid+smtp://[email protected])"
|
Транспорт round-robin начинает с рандомно выбранного транспорта, а затем переключается на следующий доступный трансфер для каждого последующего письма.
Как и с транспортом failover, round-robin повторно пытается совершить отправку, пока транспорт не добьется успеха (или пока все не потерпят неудачу). В отличие от транспорта failover, он распространяет нагрузку по всем своим транспортам.
New in version 5.1: Рандомный выбор первого транспорта был представлен в Symfony 5.1. В предыдущих версиях Symfony первый транспорт всегда выбирался первым.
Верификаиця точек TLS¶
По умолчанию, транспорт SMTP выполняет верификацию точек TLS. Это поведение
конфигурируется опцией verify_peer
. Хотя и не рекомендуется отключать
верификацию из соображений безопасности, это может быть полезно при разработке
приложения или при использовании самозаверенного сертификата:
$dsn = 'smtp://user:[email protected]?verify_peer=0';
New in version 5.1: Опция verify_peer
была представлена в Symfony 5.1.
Другие опции¶
command
Команда для выполнения транспортом
sendmail
:$dsn = 'sendmail://default?command=/usr/sbin/sendmail%20-oi%20-t'
New in version 5.2: Эта опция была представлена в Symfony 5.2.
local_domain
Имя домена для использования в команде
HELO
:$dsn = 'smtps://smtp.example.com?local_domain=example.org'
New in version 5.2: Эта опция была представлена в Symfony 5.2.
restart_threshold
Максимальное количество сообщений для отправки до перезагрузки транспорта. Может быть использована вместе с
restart_threshold_sleep
:$dsn = 'smtps://smtp.example.com?restart_threshold=10&restart_threshold_sleep=1'
New in version 5.2: Эта опция была представлена в Symfony 5.2.
restart_threshold_sleep
Количество секунд сна между остановкой и перезагрузкой транспорта. Часто сочетается с
restart_threshold
:$dsn = 'smtps://smtp.example.com?restart_threshold=10&restart_threshold_sleep=1'
New in version 5.2: Эта опция была представлена в Symfony 5.2.
ping_threshold
Минимальное количество секунд между двумя сообщениями, необходимое для пинга серверу:
$dsn = 'smtps://smtp.example.com?ping_threshold=200'
New in version 5.2: Эта опция была представлена в Symfony 5.2.
Создание и отправка сообщений¶
Для отправки письма, получите экземпляр Mailer
,
используя подсказку MailerInterface
, и создайте
объект Email
:
// 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;
class MailerController extends AbstractController
{
/**
* @Route("/email")
*/
public function sendEmail(MailerInterface $mailer): Response
{
$email = (new Email())
->from('[email protected]')
->to('[email protected]')
//->cc('[email protected]')
//->bcc('[email protected]')
//->replyTo('[email protected]')
//->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);
// ...
}
}
Вот и все! Сообщение будет отправлено через сконфигурированный вами транспорт.
Адреса электронной почты¶
Все методы, требующие адресов электронной почты (from()
, to()
, etc.),
принимают как строки, так и объекты адресов:
// ...
use Symfony\Component\Mime\Address;
$email = (new Email())
// адрес почты как обычная строка
->from('[email protected]')
// адрес почты как объект
->from(new Address('[email protected]'))
// определение адреса и имени почты как объекта
// (почтовые клиенты отобразят имя)
->from(new Address('[email protected]', 'Fabien'))
// определение адреса и имени почты как строки
// (формат должен соответствовать: 'Имя <[email protected]>')
->from(Address::create('Fabien Potencier <[email protected]>'))
// ...
;
Tip
Вместо вызова ->from()
каждый раз при создании письма, вы можете
сконфигурировать письма глобально,
чтобы установить одну и ту же From
письма во всех сообщениях.
Note
Локальная часть адреса (то, что перед @
) может включать в себя символы UTF-8,
кроме адреса отправителя (чтобы избежать ошибок с возвращенными письмами). Например:
föóbàr@example.com
, 用户@example.com
, θσερ@example.com
, и т.д.
New in version 5.2: Поддержка символов UTF-8 в адресах почты была представлена в Symfony 5.2.
Используйте методы addTo()
, addCc()
, или addBcc()
, чтобы добавить больше адресов:
$email = (new Email())
->to('[email protected]')
->addTo('[email protected]')
->cc('[email protected]')
->addCc('[email protected]')
// ...
;
Как вариант, вы можете передать несколько адресов каждому методу:
$toAddresses = ['[email protected]', new Address('[email protected]')];
$email = (new Email())
->to(...$toAddresses)
->cc('[email protected]', '[email protected]')
// ...
;
Заголовки сообщений¶
Сообщения содержат некоторое количество полей заголовков, чтобы описать их содержание. Symfony устанавливает все заголовки автоматически, но вы также можете установить собственные заголовки. Существуют разные типы заголовков (заголовок Id, заголовок Mailbox, заголовок Date и т.д.), но в большинстве случаев вы будете устанавливать текстовые заголовки:
$email = (new Email())
->getHeaders()
// этот заголовок сообщает авто-ответчикам ("режим отпуска электронной почты")
// не отвечать на это сообщение, так как это автоматизированное письмо
->addTextHeader('X-Auto-Response-Suppress', 'OOF, DR, RN, NRN, AutoReply');
// ...
;
Tip
Вместо вызова ->addTextHeader()
каждый раз при создании письма, вы можете
сконфигурировать письма глобально,
чтобы установить одни и те же заголовки во всех отправленных письмах.
Содержание сообщения¶
Текстовое и HTML-содержание сообщений писем может быть строками (обычно в результате отображения какого-то шаблона), или PHP-источниками:
$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 далее в этой статье, чтобы узнать больше.
Прикрепление файлов¶
Используйте метод attachFromPath()
, чтобы прикреплять файлы, существующие в вашей файловой системе:
$email = (new Email())
// ...
->attachFromPath('/path/to/documents/terms-of-use.pdf')
// по желанию вы можете сообщить клиентам почты, чтобы они отображали пользовательское имя файла
->attachFromPath('/path/to/documents/privacy.pdf', 'Privacy Policy')
// по желанию вы можете предоставить ясный тип MIME (в другом случае он будет угадываться)
->attachFromPath('/path/to/documents/contract.doc', 'Contract', 'application/msword')
;
Как вариант, вы можете использовать метод attach()
, чтобы прикреплять содержание из потока:
$email = (new Email())
// ...
->attach(fopen('/path/to/documents/contract.doc', 'r'))
;
Встраивание изображений¶
Если вы хотите отобразить изображения внутри вашего письма, вы должны их встроить, а не добавлять их как вложения. При использовании Twig для отображения содержания письма, как объясняется далее в этой статье, изображения встраиваются автоматически. В другом случае, вам нужно будет встроить их вручную.
Для начала, используйте метод embed()
или embedFromPath()
, чтобы добавить
изображение из файла или потока:
$email = (new Email())
// ...
// получить содержание изображение из PHP-источника
->embed(fopen('/path/to/images/logo.png', 'r'), 'logo')
// получить содержание изображение из существующего файла
->embedFromPath('/path/to/images/signature.gif', 'footer-signature')
;
Второй необязательный аругмент обоих методов - это имя изображения (“Content-ID” по стандарту MIME). Его значение - это произвольная строка, используемая позже для того, чтобы ссылаться на изображение внутри HTML содержания:
$email = (new Email())
// ...
->embed(fopen('/path/to/images/logo.png', 'r'), 'logo')
->embedFromPath('/path/to/images/signature.gif', 'footer-signature')
// сошлитесь на изображения, используя синтаксис 'cid:' + "image embed name"
->html('<img src="cid:logo"> ... <img src="cid:footer-signature"> ...')
;
Конфигурация писем глобально¶
Вместо вызова ->from()
по каждому письму, которое вы создаете, вы можете
сконфигурировать это значение глобально, чтобы оно было установлено во всех
отправленных письмах. То же самое верно и для ->to()
, и для заголовков.
- YAML
1 2 3 4 5 6 7 8 9 10
# config/packages/dev/mailer.yaml framework: mailer: envelope: sender: '[email protected]' recipients: ['[email protected]', '[email protected]'] headers: from: 'Fabien <[email protected]>' bcc: '[email protected]' X-Custom-Header: 'foobar'
- XML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
<!-- config/packages/mailer.xml --> <?xml version="1.0" encoding="UTF-8" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:framework="http://symfony.com/schema/dic/symfony" xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> <!-- ... --> <framework:config> <framework:mailer> <framework:envelope> <framework:sender>[email protected]</framework:sender> <framework:recipients>[email protected]</framework:recipients> <framework:recipients>[email protected]</framework:recipients> </framework:envelope> <framework:header name="from">Fabien <[email protected]></framework:header> <framework:header name="bcc">[email protected]</framework:header> <framework:header name="X-Custom-Header">foobar</framework:header> </framework:mailer> </framework:config> </container>
- PHP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// config/packages/mailer.php use Symfony\Config\FrameworkConfig; return static function (FrameworkConfig $framework) { $mailer = $framework->mailer(); $mailer ->envelope() ->sender('[email protected]') ->recipients(['[email protected]', '[email protected]']) ; $mailer->header('from')->value('Fabien <[email protected]>'); $mailer->header('bcc')->value('[email protected]'); $mailer->header('X-Custom-Header')->value('foobar'); };
New in version 5.2: Опция headers
была представлена в Symfony 5.2.
Обработка ошибок отправки¶
Symfony Mailer считает отправку успешной, когда ваш транспорт (SMTP-сервер или сторонний поставщик) принимает письмо для дальнейшей доставки. Сообщение может быть утеряно или не доставлено позднее из-за проблем в вашем поставщике, но это выходит за пределы возможностей вашего приложения Symfony.
Если при передаче письма вашему транспорту произошла ошибка, Symfony вызывает
TransportExceptionInterface
.
Поймайте исключение, чтобы восстановиться после ошибки или для отображения
некого сообщения:
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
$email = new Email();
// ...
try {
$mailer->send($email);
} catch (TransportExceptionInterface $e) {
// некая ошибка предотвратила отправку письма; отобразить сообщение
// об ошибке или попробовать отправить сообщение повторно
}
Отладка писем¶
Объект SentMessage
, возвращенный методом send()
класса TransportInterface
, предоставляет
доступ к изначальному сообещнию (getOriginalMessage()
) и к некоторой информации
отладки (getDebug()
) вроде HTTP-вызовов, совершенных HTTP-транспортом, что полезно
для отладки ошибок.
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:
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
$email = (new TemplatedEmail())
->from('[email protected]')
->to(new Address('[email protected]'))
->subject('Thanks for signing up!')
// путь шаблона Twig для отображения
->htmlTemplate('emails/signup.html.twig')
// передайте переменные (name => value) to the template
->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-содержание в текст. Если у вас в
приложении установлен 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
, которое указывает на тот каталог, где
хранятся ваши изображения:
- YAML
1 2 3 4 5 6 7
# config/packages/twig.yaml twig: # ... paths: # укажите туда, где живут ваши изображения '%kernel.project_dir%/assets/images': images
- XML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<!-- config/packages/twig.xml --> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:twig="http://symfony.com/schema/dic/twig" xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/twig https://symfony.com/schema/dic/twig/twig-1.0.xsd"> <twig:config> <!-- ... --> <!-- укажите туда, где живут ваши изображения --> <twig:path namespace="images">%kernel.project_dir%/assets/images</twig:path> </twig:config> </container>
- PHP
1 2 3 4 5 6 7 8 9
// config/packages/twig.php use Symfony\Config\TwigConfig; return static function (TwigConfig $twig) { // ... // укажите туда, где живут ваши изображения $twig->path('%kernel.project_dir%/assets/images', 'images'); };
Теперь, используйте специального помощника Twig email.image()
, чтобы встроить
изображение в содержание письма:
1 2 3 4 5 | {# '@images/' refers to the Twig namespace defined earlier #}
<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
:
- YAML
1 2 3 4 5 6 7
# config/packages/twig.yaml twig: # ... paths: # укажите туда, где живут ваши css-файлы '%kernel.project_dir%/assets/styles': styles
- XML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<!-- config/packages/twig.xml --> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:twig="http://symfony.com/schema/dic/twig" xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/twig https://symfony.com/schema/dic/twig/twig-1.0.xsd"> <twig:config> <!-- ... --> <!-- укажите туда, где живут ваши css-файлы --> <twig:path namespace="styles">%kernel.project_dir%/assets/styles</twig:path> </twig:config> </container>
- PHP
1 2 3 4 5 6 7 8 9
// config/packages/twig.php use Symfony\Config\TwigConfig; return static function (TwigConfig $twig) { // ... // укажите туда, где живут ваши css-файлы $twig->path('%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
.
Цифровая подпись и шифрование сообщений¶
Существует возможность цифрового подписания и/или шифрования сообщений электронных писем для усиления их целосности/безопасности. Обе опции можно объединить для шифрования сообщения с электронной подписью и/или для цифрового подписания зашифрованного сообщения.
Перед тем, как подписывать/зашифровывать сообщения, убедитесь, что у вас есть:
- Правильно установленное и сконфигурированное расширение OpenSSL PHP ;
- Валидный сертификат безопасности S/MIME.
Tip
При использовании OpenSSL для генерирования сертификатов, не забудьте добавить
опцию команды -addtrust emailProtection
.
Цифровая подпись сообщений¶
При добавлении цифровой подписи к сообщению, генерируется криптографический хеш для всего содержания сообщения (включая вложения). Этот хеш добавляется как вложение, чтобы получатель мог валидировать целосность полученного сообщения. Однако, содержание изначального сообщения остается читаемым для почтовых агентов, не поддерживающих подписанные сообщения, поэтому вы также должны зашифровать сообщение, если вы хотите скрыть его содержание.
Вы можете подписывать сообщения используя S/MIME
или DKIM
. В обоих случаях,
сертификат и приватный ключ долшжны быть PEM-зашифрованы, и могут быть либо созданы
с использованием, к примеру, OpenSSL, либо получены у официальной Сертификационной
компании (CA).
Получатель письма должен иметь сертификат CA в списке доверенных лиц, чтобы верифицировать
подпись.
Подпись S/MIME¶
S/MIME стандартна для шифрования публичных ключей и подписания данных MIME. Она требует использование как сертификата, так и приватного ключа:
use Symfony\Component\Mime\Crypto\SMimeSigner;
use Symfony\Component\Mime\Email;
$email = (new Email())
->from('[email protected]')
// ...
->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 - это метод аутентификации письма, который добавляет цифровую подпись, ссылающуюся на основной домен, к каждому исходящему письму. Он требует приватный ключ, но не сертификат:
use Symfony\Component\Mime\Crypto\DkimSigner;
use Symfony\Component\Mime\Email;
$email = (new Email())
->from('[email protected]')
// ...
->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()
);
New in version 5.2: Подпись DKIM была представлена в Symfony 5.2.
Шифрование сообщений¶
При шифровании сообщения, все сообщения (включая вложения) шифруется с использованием сертификата. Следовательно, только получатели, имеющие соответствующий приватный ключ, могут прочитать содержание изначального сообщения:
use Symfony\Component\Mime\Crypto\SMimeEncrypter;
use Symfony\Component\Mime\Email;
$email = (new Email())
->from('[email protected]')
// ...
->html('...');
$encrypter = new SMimeEncrypter('/path/to/certificate.crt');
$encryptedEmail = $encrypter->encrypt($email);
// теперь используйте компонент Mailer, чтобы отправить этот $encryptedEmail вместо изначального письма
Вы можете передать больше одного сертификата конструктору SMimeEncrypter
, и
он выберет соответствующий сертификат, в зависимости от опции To
:
$firstEmail = (new Email())
// ...
->to('[email protected]');
$secondEmail = (new Email())
// ...
->to('[email protected]');
// второй необязательный аргумент SMimeEncrypter определяет, какой алгоритм шифрования используется
// (должен быть одной из этих констант: https://www.php.net/manual/en/openssl.ciphers.php)
$encrypter = new SMimeEncrypter([
// ключ = получатель письма; значение = путь к файлу сертификата
'[email protected]' => '/path/to/first-certificate.crt',
'[email protected]' => '/path/to/second-certificate.crt',
]);
$firstEncryptedEmail = $encrypter->encrypt($firstEmail);
$secondEncryptedEmail = $encrypter->encrypt($secondEmail);
Несколько транспортов писем¶
Вы можете захотеть использовать более одного транспорта почтовой программы для
доставки ваших сообщений. Это можно сконфигурировать, заменив запись конфигурации
dsn
на запись transports
, следующим образом:
- YAML
1 2 3 4 5 6
# config/packages/mailer.yaml framework: mailer: transports: main: '%env(MAILER_DSN)%' alternative: '%env(MAILER_DSN_IMPORTANT)%'
- XML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
<!-- config/packages/mailer.xml --> <?xml version="1.0" encoding="UTF-8" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:framework="http://symfony.com/schema/dic/symfony" xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> <!-- ... --> <framework:config> <framework:mailer> <framework:transport name="main">%env(MAILER_DSN)%</framework:transport> <framework:transport name="alternative">%env(MAILER_DSN_IMPORTANT)%</framework:transport> </framework:mailer> </framework:config> </container>
- PHP
1 2 3 4 5 6 7 8 9
// config/packages/mailer.php use Symfony\Config\FrameworkConfig; return static function (FrameworkConfig $framework) { $framework->mailer() ->transport('main', '%env(MAILER_DSN)%') ->transport('alternative', '%env(MAILER_DSN_IMPORTANT)%') ; };
По умолчанию используется первый транспорт. Другие транспорты могут быть использованы
путем добавления к письму текстового заголовка X-Transport
:
// Отправить, используя первый "главный" транспорт ...
$mailer->send($email);
// ... или использовать "альтернативный"
$email->getHeaders()->addTextHeader('X-Transport', 'alternative');
$mailer->send($email);
Асинхронная отправка сообщений¶
Когда вы вызываете $mailer->send($email)
, письмо сразу же отправляется транспорту.
Для улучшения производительности, вы можете использовать преимущества Мессенджера,
чтобы отправлять сообщения позже через транспорт Мессенджера.
Начните, следуя документации Мессенджера и сконфигурировав транспорт.
Когда все будет настроено, при вызове $mailer->send()
, сообщение
SendEmailMessage
будет запущено через
автобус сообщений по умолчанию (messenger.default_bus
). Предполагая, что у вас
есть транспорт под названием async
, вы можете маршрутизировать сообщение в него:
- YAML
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
- XML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
<!-- config/packages/messenger.xml --> <?xml version="1.0" encoding="UTF-8" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:framework="http://symfony.com/schema/dic/symfony" xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> <framework:config> <framework:messenger> <framework:transport name="async">%env(MESSENGER_TRANSPORT_DSN)%</framework:transport> <framework:routing message-class="Symfony\Component\Mailer\Messenger\SendEmailMessage"> <framework:sender service="async"/> </framework:routing> </framework:messenger> </framework:config> </container>
- PHP
1 2 3 4 5 6 7 8 9 10 11
// config/packages/messenger.php use Symfony\Config\FrameworkConfig; return static function (FrameworkConfig $framework) { $framework->messenger() ->transport('async')->dsn('%env(MESSENGER_TRANSPORT_DSN)%'); $framework->messenger() ->routing('Symfony\Component\Mailer\Messenger\SendEmailMessage') ->senders(['async']); };
Благодаря этому, вместо немедленной доставки, сообщения будут отправлены транспорту для обработки позже (см. Consuming Messages (Running the Worker)).
Вы можете сконфигурировать, какой автобус используется для запуска сообщения,
используя опцию message_bus
. Вы также можете установить ее как false
,
чтобы вызвать транспорт Mailer напрямую и отключить асихнронную доставку.
- YAML
1 2 3 4
# config/packages/mailer.yaml framework: mailer: message_bus: app.another_bus
- XML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
<!-- config/packages/messenger.xml --> <?xml version="1.0" encoding="UTF-8" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:framework="http://symfony.com/schema/dic/symfony" xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> <framework:config> <framework:mailer message_bus="app.another_bus" > </framework:mailer> </framework:config> </container>
- PHP
1 2 3 4 5 6 7
// config/packages/mailer.php use Symfony\Config\FrameworkConfig; return static function (FrameworkConfig $framework) { $framework->mailer() ->messageBus('app.another_bus'); };
New in version 5.1: Опция message_bus
была представлена в Symfony 5.1.
Добавление тегов и метаданных к письмам¶
New in version 5.1: Классы TagHeader
и
MetadataHeader
были
представлены в Symfony 5.1.
Определеные сторонние транспорты поддерживают теги и метаданные писем,
что может быть использовано для группирования, отслеживания и рабочих процессов.
Вы можете добавить их, используя классы
TagHeader
и
MetadataHeader
. Если ваш транспорт
поддерживает заголовки, он преобразует их в соответствующий формат:
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
New in version 5.4: Поддержка тегов и метаданных для Sendgrid была представлена в Symfony 5.4.
Следующий транспорт поддерживает только теги:
- OhMySMTP
Разработка и отладка¶
Отключение доставки¶
Во время разработки (или тестирования), вы можете захотеть отключить доставку
сообщений полностью. Вы можете сделать это, используя null://null
как DSN
почтовой программы, либо в ваших
файлах конфигурации .env, либо в файле
конфигурации почтовой программы (например, в окружениях dev
или test
):
- YAML
1 2 3 4
# config/packages/dev/mailer.yaml framework: mailer: dsn: 'null://null'
- XML
1 2 3 4 5 6 7 8 9 10 11 12 13 14
<!-- config/packages/mailer.xml --> <?xml version="1.0" encoding="UTF-8" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:framework="http://symfony.com/schema/dic/symfony" xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> <!-- ... --> <framework:config> <framework:mailer dsn="null://null"/> </framework:config> </container>
- PHP
1 2 3 4 5 6 7 8
// config/packages/mailer.php use Symfony\Config\FrameworkConfig; return static function (FrameworkConfig $framework) { // ... $framework->mailer() ->dsn('null://null'); };
Note
Если вы используете Мессенджер и машрутизируете его к транспорту, сообщение все равно будет отправлено этому транспорту.
Постоянная отправка письма по одному адресу¶
Вместо полного отключения доставки, вы можете захотеть всегда отправлять письма по одному конкретному адресу, вместо настоящего адреса:
- YAML
1 2 3 4 5
# config/packages/dev/mailer.yaml framework: mailer: envelope: recipients: ['[email protected]']
- XML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
<!-- config/packages/mailer.xml --> <?xml version="1.0" encoding="UTF-8" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:framework="http://symfony.com/schema/dic/symfony" xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> <!-- ... --> <framework:config> <framework:mailer> <framework:envelope> <framework:recipient>[email protected]</framework:recipient> </framework:envelope> </framework:mailer> </framework:config> </container>
- PHP
1 2 3 4 5 6 7 8 9 10
// config/packages/mailer.php use Symfony\Config\FrameworkConfig; return static function (FrameworkConfig $framework) { // ... $framework->mailer() ->envelope() ->recipients(['[email protected]']) ; };
Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.