Почему важна отложенная отправка писем в WooCommerce
В интернет-магазинах на WooCommerce при большом количестве заказов массовая отправка писем (подтверждения, уведомления о статусе, счета) может привести к:
- Перегрузке сервера хостинга из-за большого количества SMTP-сессий;
- Блокировке почтового сервиса за подозрительную активность;
- Замедлению работы сайта из-за синхронной отправки email;
- Потере писем при ошибках в процессе отправки.
Отложенная отправка (email queue) помогает сгладить пики нагрузки, распределяя отправку писем во времени.
Диагностика проблем с отправкой писем WooCommerce
- Проверьте логи почтового сервера и плагинов отправки (например, WP Mail Logging) — видны ли ошибки подключения или превышение лимитов?
- Оцените нагрузку на PHP-процессы во время массовой отправки писем.
- Подтвердите, что письма действительно уходят, но с задержкой или не уходят вовсе.
Пример логов ошибок при массовой отправке:
SMTP connect() failed. https://github.com/PHPMailer/PHPMailer/wiki/TroubleshootingПошаговое решение: реализация очереди отправки писем в WooCommerce
1. Отключаем синхронную отправку писем WooCommerce
По умолчанию WooCommerce отправляет письма сразу после события. Чтобы перейти к очереди, нужно перехватить отправку писем и поставить их в очередь.
2. Создаем таблицу для хранения очереди писем
function create_email_queue_table() {
global $wpdb;
$table_name = $wpdb->prefix . 'email_queue';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE IF NOT EXISTS $table_name (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
to_email VARCHAR(255) NOT NULL,
subject VARCHAR(255) NOT NULL,
message LONGTEXT NOT NULL,
headers LONGTEXT,
attachments LONGTEXT,
status VARCHAR(20) DEFAULT 'pending',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
}
add_action('after_switch_theme', 'create_email_queue_table');3. Перехватываем отправку писем WooCommerce и сохраняем в очередь
add_filter('woocommerce_email_actions', function($actions) {
// Удаляем стандартные хуки отправки писем, чтобы контролировать самостоятельно
return [];
});
add_action('woocommerce_order_status_completed', 'queue_order_completed_email', 10, 1);
function queue_order_completed_email($order_id) {
$order = wc_get_order($order_id);
if (!$order) return;
$mailer = WC()->mailer();
$email = $mailer->get_emails()['WC_Email_Customer_Completed_Order'];
if (!$email) return;
$to = $order->get_billing_email();
$subject = $email->get_subject();
$message = $email->get_content_html();
$headers = $email->get_headers();
$attachments = maybe_serialize($email->get_attachments());
global $wpdb;
$table = $wpdb->prefix . 'email_queue';
$wpdb->insert($table, [
'to_email' => $to,
'subject' => $subject,
'message' => $message,
'headers' => $headers,
'attachments' => $attachments,
'status' => 'pending',
'created_at' => current_time('mysql')
]);
}4. Создаем WP-Cron задачу для отправки писем из очереди
add_action('send_email_queue', 'process_email_queue');
function process_email_queue() {
global $wpdb;
$table = $wpdb->prefix . 'email_queue';
$emails = $wpdb->get_results("SELECT * FROM $table WHERE status = 'pending' ORDER BY created_at ASC LIMIT 10");
if (!$emails) return;
foreach ($emails as $email) {
$sent = wp_mail($email->to_email, $email->subject, $email->message, maybe_unserialize($email->headers), maybe_unserialize($email->attachments));
if ($sent) {
$wpdb->update($table, ['status' => 'sent'], ['id' => $email->id]);
} else {
$wpdb->update($table, ['status' => 'failed'], ['id' => $email->id]);
}
}
}
if (!wp_next_scheduled('send_email_queue')) {
wp_schedule_event(time(), 'five_minutes', 'send_email_queue');
}
add_filter('cron_schedules', function($schedules){
$schedules['five_minutes'] = [
'interval' => 300,
'display' => __('Every 5 Minutes')
];
return $schedules;
});Как проверить, что отложенная отправка работает
- Создайте тестовый заказ и переведите его в статус «Выполнен» — письмо не должно уйти мгновенно.
- Проверьте таблицу
wp_email_queue— в ней должен появиться новый записанный email с статусомpending. - Дождитесь запуска WP-Cron (или запустите вручную:
wget -q -O - 'https://example.com/wp-cron.php?doing_wp_cron') и проверьте обновление статуса наsentилиfailed. - Проверьте почту получателя — письмо должно прийти с задержкой.
Частые ошибки и их исправление
- Письма не отправляются — WP-Cron не работает. Проверьте, включен ли WP-Cron на сайте. Если нет, настройте системный cron-задание для вызова wp-cron.php.
- Письма висят в статусе
pending. Возможно, крон-задача не запущена. Вызовите ее вручную или проверьте логи ошибок. - Письма в статусе
failed. Проверьте правильность параметровwp_mail()и корректность заголовков, вложений. - Скрипт не создает таблицу очереди. Убедитесь, что функция создания таблиц вызывается, например, при смене темы или вручную.
Практические советы по безопасности и производительности
- Ограничивайте количество писем за один проход (например, 10), чтобы не нагружать сервер.
- Используйте транзакции и блокировки для предотвращения одновременной обработки одних и тех же писем несколькими процессами.
- Сохраняйте в базе только необходимые данные, не храните пароли и конфиденциальную информацию в письмах.
- Для повышения надежности используйте внешние сервисы отправки SMTP или API (Postmark, SendGrid) совместно с очередью.
- Включайте логирование отправленных писем и ошибок для оперативного мониторинга.
Сравнение вариантов реализации отложенной отправки
| Метод | Плюсы | Минусы | Пример |
|---|---|---|---|
| Плагин WP Offload SES или аналогичный | Простота, интеграция с сервисом SMTP/API, отслеживание | Зависимость от стороннего сервиса, стоимость | Clearfy Pro (оптимизация и чистка) |
| Собственная очередь на базе базы данных и WP-Cron | Полный контроль, бесплатность, гибкость | Необходимость поддержки и доработок, нагрузка на базу | Код из статьи |
| Использование системного cron и внешних очередей (Redis, RabbitMQ) | Максимальная производительность, масштабируемость | Сложность настройки, требует серверного доступа | Требует отдельной реализации |