Отправка писем
Внезапный дополнительный выпуск в уже оконченном курсе!
Fenom будет готовить шаблоны писем, Emogrifier встраивать стили непосредственно в элементы (чтобы всё правильно читалось в почтовых клиентах), а PHPMailer будет отправлять.
Добавляем новые зависимости в проект:
composer require phpmailer/phpmailer fenom/fenom pelago/emogrifier
Подготовка
Я работаю в Docker и поэтому еще добавляю контейнер с Mailhog в свой docker-compose.yml. Это будет локальный получатель всем писем, для тестирования.:
mailhog:
image: teawithfruit/mailhog
ports:
- 8090:8025
Дальше все настройки будут указаны для окружения в Docker, так проще.
Теперь добавляем новые переменные окружения: директорию для хранения шаблонов и настройки SMTP. Редактируем наш .env файл:
TEMPLATE_DIR=/vesp/core/templates/
SMTP_HOST=mailhog
SMTP_USER=no-reply@vesp-shop.bezumkin.ru
SMTP_USER_NAME=no-reply
SMTP_PASS=
SMTP_PORT=1025
SMTP_PROTO=
Теперь создаём новый служебный класс src/Services/Fenom.php, он расширит оригинальный Fenom:
<?php
namespace App\Services;
class Fenom extends \Fenom
{
public function __construct()
{
// При инициализации класса передаём файловый провайдер,
// нацеленный в нашу директорию с шаблонами
parent::__construct(new \Fenom\Provider(getenv('TEMPLATE_DIR')));
// Создаём директорию для кэша, если нужно
$cache = getenv('CACHE_DIR') . 'fenom/';
if (!file_exists($cache) && !mkdir($cache) && !is_dir($cache)) {
throw new \RuntimeException(sprintf('Directory "%s" was not created', $cache));
}
$this->setCompileDir($cache);
// Указываем настройки Fenom, согласно документации
$this->setOptions(self::DENY_NATIVE_FUNCS | self::AUTO_RELOAD | self::FORCE_VERIFY | self::AUTO_ESCAPE);
$this->addAllowedFunctions(['print_r']);
}
}
Работа с шаблонизацией окончена. Теперь создаём служебный класс для почты в src/Services/Mail.php:
<?php
namespace App\Services;
use Pelago\Emogrifier\CssInliner;
use PHPMailer\PHPMailer\Exception;
use PHPMailer\PHPMailer\PHPMailer;
class Mail
{
public function send(string $to, string $subject, string $tpl, ?array $data = []): ?string
{
// Мы работаем только через SMTP, никакой локальной отправки
// Если хост не указан - значит отправка почты не нужна, просто выходим без ошибки
if (!getenv('SMTP_HOST')) {
return null;
}
$mail = new PHPMailer(true);
$mail->CharSet = 'UTF-8';
// Основные настройки транспорта
$mail->isSMTP();
$mail->Host = getenv('SMTP_HOST');
$mail->SMTPAuth = (bool)getenv('SMTP_USER');
$mail->Username = getenv('SMTP_USER');
$mail->Password = getenv('SMTP_PASS');
$mail->SMTPSecure = getenv('SMTP_PROTO');
$mail->Port = getenv('SMTP_PORT');
$mail->SMTPDebug = 0;
$mail->isHTML();
$mail->Subject = $subject;
try {
// Указываем получателя и отправителя
$mail->addAddress($to);
$mail->setFrom(getenv('SMTP_USER'), getenv('SMTP_USER_NAME'));
// Содержимое письма генерируется из шаблона Fenom
$body = (new Fenom())->fetch($tpl, $data);
// При выставлении содержимого, мы распихиваем общие стили HTML по элементам
$mail->Body = CssInliner::fromHtml($body)->inlineCss()->render();
// Добавляем версию без HTML, для поддержки древних клиентов - нам не трудно
$mail->AltBody = $mail->html2text(nl2br($mail->Body));
// Отправляем
$mail->send();
return null;
} catch (Exception $e) {
return $mail->ErrorInfo;
} catch (\Exception $e) {
return $e->getMessage();
}
}
}
В этом классе единственный метод send(), который вернёт или текст ошибки, или null, если отправка прошла успешно.
Работа с шаблонами
Я предпочитаю прописать один-единственный общий шаблон для писем по-умолчанию, чтобы потом его расширять.
Полностью публиковать его не буду, но вот общие правила:
- Это HTML файл, там что там должен быть <!DOCTYPE HTML>, теги head, body и т.д.
- Стили пишем внутри тега head в style, они будут разобраны Emogrifier и добавлены элементам в body
- Вёрстка таблицами, никакого flexbox и CSS 3 - почтовики это не любят.
- Картинки только JPG и PNG, с абсоютными ссылками.
- В шаблоне нужно предусмотреть места расширения Fenom тегами {block}
- Соответственно, везде можно использовать теги Fenom, например для доступа к переменным окружения
То есть, у вас должно быть что-то подобное в core/templates/email.tpl:
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>{$.env.APP_NAME}</title>
<style>
body {
background: #f7f7f7;
margin: 0;
padding: 0;
width: 100%;
height: 100%;
font-family: Arial, serif;
font-size: 14px;
color: #000;
}
{block 'style'}{/block}
</style>
</head>
<body>
<table>
<tbody>
<tr>
<td class="content">
{block 'content'}{/block}
</td>
</tr>
<tr>
<td>
<a href="{$.env.SITE_URL}" target="_blank">{$.env.APP_NAME}</a>
</td>
</tr>
</tbody>
</table>
</body>
</html>
Общий шаблон есть, теперь нужен шаблончик уведомления о поступившем заказе. Кладём рядом
email-order-new.tpl:
{extends 'email.tpl'}
{block 'content'}
<pre>{print_r($data, true)}</pre>
{/block}
Расширяем и печатаем переменную $data. Дальше сами оформите по вкусу.
Отправка писем
Почти всё готово, осталось добавить отправку пичем в контроллер создания заказа.
public function put(): ResponseInterface
{
// ... до этих строк всё остаётся как было
if ($order->save()) {
$order->orderProducts()->createMany($orderProducts);
}
// В конце добавляем отправку почты
if ($order->email) {
$error = (new \App\Services\Mail())->send(
$order->email,
'Спасибо за ваш заказ!',
'email-order-new.tpl', // шаблон указывается относительно директории Fenom
['data' => $order->toArray()] // В шаблоне передаём массив заказа
);
// Почта вернёт null, или текст ошибки
if ($error) {
return $this->failure($error);
}
}
return $this->success();
}
Результат смотрим по адресу контейнера Mailhog, или куда вы там письмо отправили:
При просмотре исходников письма можно заметить, что тег style пропал, а сами стили переехали в элементы - это и есть работа Emogrifier.
Вот и вся работа с письмами. Остаётся только отладить шаблоны и вывод содержимого. Добавить товары, картинки и т.д.
Все изменения одним коммитом в репозитории.
0
👍
👎
❤️
🔥
😮
😢
😀
😡
348
28.05.2023, 06:37:22
4 комментария
31.05.2023, 17:12:24
Василий, спасибо!
А не будут ли письма попадать под спам фильтр если использовать PHPMailer?
Может лучше отправлять письма, как ты учил нас на MODX, через smtp.yandex.ru?
31.05.2023, 18:52:40
PHPMailer - это просто библиотека для работы с почтой, как через SMTP, так и без. Она же используется и в MODX.
В моём примере без настроек SMTP вообще ничего не отправляется, так что по-любому надо использовать или Яндекс, или Gmail, или что-то еще.
31.05.2023, 21:12:24
Понял, спасибо!
Это сообщение было удалено
bezumkin.ru
Личный сайт Василия Наумкина
Прямой эфир
Василий Наумкин
01.07.2024, 11:56:41
Да, верно, именно так.
А в контроллере, скорее всего, ловить данные методом post.
Василий Наумкин
26.06.2024, 09:38:15
О, точно, вылезает если не залогинен.
Спасибо, исправил!
Василий Наумкин
09.04.2024, 04:45:01
> Ошибка 500
Это не похоже на ошибку Nginx, это скорее всего ошибка PHP - надо смотреть его логи.
...
russel gal
09.03.2024, 20:17:18
> А этот стоило написать хотя бы затем, чтобы получить комментарий от юзера, который ничего не писал...
Александр Наумов
27.01.2024, 03:06:18
Василий, спасибо!
Извини, тупанул.