← Назад к вопросам

Как реализовать отправку огромного числа писем с помощью класса, принимающего один email и отправляющего письмо?

2.0 Middle🔥 72 комментариев
#Архитектура и паттерны#Очереди и брокеры сообщений

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Реализация массовой отправки писем в PHP

Для эффективной отправки огромного числа писем с помощью класса, работающего с одиночными email, необходимо реализовать систему асинхронной обработки и очередей задач. Прямой последовательный вызов метода для каждого адреса приведет к проблемам с производительностью, таймаутами и блокировкой процесса.

Архитектурное решение

Ключевая идея - отделить генерацию задач отправки от непосредственного выполнения. Вот полная реализация:

<?php

class EmailSender {
    private $mailer;
    
    public function __construct() {
        // Инициализация почтового клиента (PHPMailer, Symfony Mailer и т.д.)
        $this->mailer = new \PHPMailer\PHPMailer\PHPMailer();
        $this->mailer->isSMTP();
        $this->mailer->Host = 'smtp.example.com';
        $this->mailer->SMTPAuth = true;
        // ... остальная конфигурация
    }
    
    /**
     * Оригинальный метод отправки одного письма
     */
    public function sendSingleEmail(string $to, string $subject, string $body): bool {
        try {
            $this->mailer->clearAddresses();
            $this->mailer->addAddress($to);
            $this->mailer->Subject = $subject;
            $this->mailer->Body = $body;
            
            return $this->mailer->send();
        } catch (\Exception $e) {
            error_log("Email sending failed: " . $e->getMessage());
            return false;
        }
    }
}

class BulkEmailDispatcher {
    private $queueService;
    private $batchSize = 100;
    private $delayBetweenBatches = 1; // секунды
    
    public function __construct(QueueServiceInterface $queueService) {
        $this->queueService = $queueService;
    }
    
    /**
     * Основной метод для массовой отправки
     */
    public function dispatchMassEmails(array $recipients, string $subject, string $bodyTemplate): void {
        // 1. Разделение на батчи
        $batches = array_chunk($recipients, $this->batchSize);
        
        foreach ($batches as $batchIndex => $batch) {
            // 2. Помещение каждой задачи в очередь
            foreach ($batch as $email) {
                $task = [
                    'email' => $email,
                    'subject' => $this->personalizeSubject($subject, $email),
                    'body' => $this->personalizeBody($bodyTemplate, $email),
                    'attempt' => 0,
                    'created_at' => time()
                ];
                
                $this->queueService->push('email_sending', $task);
            }
            
            // 3. Небольшая задержка между батчами для избежания перегрузки
            if ($batchIndex < count($batches) - 1) {
                sleep($this->delayBetweenBatches);
            }
        }
    }
    
    /**
     * Воркер для обработки очереди
     */
    public function processQueueWorker(): void {
        while (true) {
            $task = $this->queueService->pop('email_sending');
            
            if (!$task) {
                sleep(5); // Пауза при пустой очереди
                continue;
            }
            
            $this->processEmailTask($task);
        }
    }
    
    private function processEmailTask(array $task): void {
        $sender = new EmailSender();
        $success = $sender->sendSingleEmail(
            $task['email'],
            $task['subject'],
            $task['body']
        );
        
        if (!$success && $task['attempt'] < 3) {
            // Повторная попытка с экспоненциальной задержкой
            $task['attempt']++;
            $task['next_attempt'] = time() + pow(2, $task['attempt']) * 60;
            $this->queueService->push('email_sending_retry', $task);
        }
    }
    
    private function personalizeSubject(string $subject, string $email): string {
        // Персонализация темы письма
        return str_replace('{email}', $email, $subject);
    }
    
    private function personalizeBody(string $template, string $email): string {
        // Персонализация тела письма
        $personalized = str_replace('{email}', $email, $template);
        $personalized = str_replace('{date}', date('Y-m-d'), $personalized);
        return $personalized;
    }
}

Ключевые компоненты системы

1. Очередь задач (Queue Service)

Используйте RabbitMQ, Redis, Beanstalkd или базу данных:

interface QueueServiceInterface {
    public function push(string $queue, array $data): bool;
    public function pop(string $queue): ?array;
}

class RedisQueueService implements QueueServiceInterface {
    private $redis;
    
    public function __construct() {
        $this->redis = new Redis();
        $this->redis->connect('127.0.0.1', 6379);
    }
    
    public function push(string $queue, array $data): bool {
        return $this->redis->lPush($queue, json_encode($data)) > 0;
    }
    
    public function pop(string $queue): ?array {
        $data = $this->redis->rPop($queue);
        return $data ? json_decode($data, true) : null;
    }
}

2. Запуск воркеров

Запустите несколько процессов для параллельной обработки:

# Запуск 5 воркеров
for i in {1..5}; do
    php worker.php &
done

3. Мониторинг и обработка ошибок

class EmailMonitoring {
    private $stats = [
        'sent' => 0,
        'failed' => 0,
        'retried' => 0
    ];
    
    public function trackSuccess(): void {
        $this->stats['sent']++;
        $this->logToFile('success.log', 'Email sent successfully');
    }
    
    public function trackFailure(string $email, string $error): void {
        $this->stats['failed']++;
        $this->logToFile('errors.log', "Failed: $email - $error");
    }
}

Оптимизации для огромных объемов

  1. Коннекшен пулинг - переиспользование SMTP соединений
  2. Асинхронное выполнение через ReactPHP или AMPHP:
// Пример с ReactPHP
$loop = React\EventLoop\Factory::create();

foreach ($emails as $email) {
    $loop->futureTick(function () use ($email, $sender) {
        $sender->sendSingleEmail($email, $subject, $body);
    });
}

$loop->run();
  1. Шардирование по нескольким SMTP серверам для обхода лимитов
  2. Пакетная обработка DNS запросов для валидации email

Рекомендации по реализации

  • Используйте готовые решения: Laravel Queues, Symfony Messenger
  • Настройте лимиты: не более 50-100 писем в минуту на один SMTP
  • Внедрите отложенную отправку для разных часовых поясов
  • Реализуйте механизм паузы/возобновления для длительных рассылок
  • Всегда добавляйте заголовки List-Unsubscribe для соблюдения CAN-SPAM Act

Такая архитектура позволяет отправлять миллионы писем, сохраняя отзывчивость основного приложения и обеспечивая надежную доставку с механизмом повторов.