Как реализовать асинхронную отправку email в PHP-приложении?

«Как реализовать асинхронную отправку email в PHP-приложении?» — вопрос из категории Архитектура, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Прямая синхронная отправка почты из скрипта блокирует его выполнение и создает риск неудачи при проблемах с SMTP-сервером. В production-среде я использую паттерн очереди задач (queue).

Стандартное решение — отдельный worker-процесс:

  1. Основное приложение помещает задачу на отправку в очередь (например, Redis или RabbitMQ).
// Пример с Redis (используя библиотеку predis/predis)
$redis = new PredisClient();
$emailData = [
    'to' => 'user@example.com',
    'subject' => 'Ваш заказ подтвержден',
    'template' => 'order_confirmed',
    'data' => ['orderId' => 12345]
];
// Добавляем задачу в список 'emails'
$redis->lpush('emails', json_encode($emailData));
// Скрипт пользователя продолжает работу, не дожидаясь отправки
  1. Отдельный worker-скрипт (запущенный, например, через Supervisor) постоянно мониторит эту очередь.
// worker.php
while (true) {
    $task = $redis->brpop('emails', 0); // Блокирующее ожидание задачи
    $emailData = json_decode($task[1], true);

    // Логика формирования и отправки письма
    $mailer = new MyAppMailer();
    try {
        $mailer->send($emailData);
        // Логируем успех
    } catch (Exception $e) {
        // Логируем ошибку, можно положить задачу в очередь для повторных попыток
        $redis->lpush('emails_failed', $task[1]);
    }
}

Почему это лучше прямого exec() или SwiftMailer?

  • Надежность: Задачи не теряются при падении воркера, они остаются в очереди.
  • Масштабируемость: Можно запустить несколько воркеров для обработки пиковой нагрузки.
  • Контроль: Легко реализовать повторные попытки (retry), логирование и мониторинг.
  • Разделение ответственности: Основное приложение не зависит от скорости или доступности почтового сервиса.