В WooCommerce вложения в письмах часто добавляют «в лоб»: подключают хук woocommerce_email_attachments и прикрепляют файл ко всем уведомлениям подряд. На практике это быстро приводит к лишним письмам с тяжелыми вложениями, жалобам от клиентов и проблемам с доставкой. Гораздо полезнее привязать файл только к нужным статусам заказа: например, к completed, processing или кастомному статусу, если вы отправляете договор, чек, инструкцию или акт только после определенного события.
Ниже разберем рабочую схему: как понять, где именно ломается логика, как ограничить вложения по статусу заказа, как проверить результат и какие ошибки встречаются чаще всего.
Когда вложение уходит не туда
Типичный сценарий выглядит так: вы добавили PDF в письмо, но он приходит и на new order админу, и на customer_on_hold_order, и на письмо о возврате. Иногда файл вообще прикрепляется к каждому письму, включая служебные уведомления. Это не только лишняя нагрузка, но и риск утечки документов, если вложение должно видеть только покупатель после оплаты.
Что проверить до правки кода
- какой именно email-тип отправляет WooCommerce в вашем сценарии;
- какой статус заказа должен запускать вложение;
- один файл нужен всем письмам или только клиентским;
- файл лежит в
wp-content/uploadsи доступен по пути на сервере; - нет ли второго плагина, который тоже добавляет вложения в письма.
Если вложение уже добавлялось через настройки плагина, сначала отключите этот механизм. Иначе вы будете отлаживать не свой код, а конфликт двух источников вложений.
Как ограничить вложение по статусу заказа
Самый надежный вариант — проверять статус заказа внутри фильтра woocommerce_email_attachments. Этот фильтр получает массив вложений, объект письма и объект заказа. Этого достаточно, чтобы прикрепить файл только к нужным уведомлениям.
Ниже пример для functions.php дочерней темы или небольшого mu-plugin. Он добавляет PDF только в письмо клиенту после завершения заказа.
<?php
add_filter( 'woocommerce_email_attachments', 'wpmail_attach_invoice_for_completed_order', 10, 4 );
function wpmail_attach_invoice_for_completed_order( $attachments, $email_id, $order, $email ) {
if ( ! $order instanceof WC_Order ) {
return $attachments;
}
// Только для письма клиенту о завершенном заказе.
if ( 'customer_completed_order' !== $email_id ) {
return $attachments;
}
if ( 'completed' !== $order->get_status() ) {
return $attachments;
}
$file_path = WP_CONTENT_DIR . '/uploads/invoices/order-completed.pdf';
if ( file_exists( $file_path ) ) {
$attachments[] = $file_path;
}
return $attachments;
}
Если нужно привязать вложение к нескольким статусам, удобнее явно перечислить их в массиве. Так код остается читаемым и не превращается в набор вложенных if.
<?php
add_filter( 'woocommerce_email_attachments', 'wpmail_attach_file_for_selected_statuses', 10, 4 );
function wpmail_attach_file_for_selected_statuses( $attachments, $email_id, $order, $email ) {
if ( ! $order instanceof WC_Order ) {
return $attachments;
}
$allowed_email_ids = array( 'customer_processing_order', 'customer_completed_order' );
$allowed_statuses = array( 'processing', 'completed' );
if ( ! in_array( $email_id, $allowed_email_ids, true ) ) {
return $attachments;
}
if ( ! in_array( $order->get_status(), $allowed_statuses, true ) ) {
return $attachments;
}
$file_path = WP_CONTENT_DIR . '/uploads/docs/order-guide.pdf';
if ( is_readable( $file_path ) ) {
$attachments[] = $file_path;
}
return $attachments;
}
Если нужен не один файл, а разные вложения для разных статусов
В реальных магазинах часто требуется не просто «прикрепить PDF», а выбрать файл по статусу. Например, после оплаты отправлять инструкцию, после завершения — гарантийный талон, а после ручной проверки — акт. В этом случае лучше собрать карту соответствий и не дублировать код.
<?php
add_filter( 'woocommerce_email_attachments', 'wpmail_attach_files_by_status_map', 10, 4 );
function wpmail_attach_files_by_status_map( $attachments, $email_id, $order, $email ) {
if ( ! $order instanceof WC_Order ) {
return $attachments;
}
$map = array(
'customer_processing_order' => array(
'processing' => WP_CONTENT_DIR . '/uploads/docs/processing-guide.pdf',
),
'customer_completed_order' => array(
'completed' => WP_CONTENT_DIR . '/uploads/docs/warranty.pdf',
),
);
if ( empty( $map[ $email_id ][ $order->get_status() ] ) ) {
return $attachments;
}
$file_path = $map[ $email_id ][ $order->get_status() ];
if ( is_readable( $file_path ) ) {
$attachments[] = $file_path;
}
return $attachments;
}
Такой подход проще сопровождать: если меняется документ для статуса processing, вы правите одну строку, а не переписываете условия в нескольких местах.
Диагностика: как понять, почему вложение не прикрепилось
Если письмо уходит без файла, сначала проверьте не WooCommerce, а путь к документу. В большинстве случаев проблема банальна: неверный абсолютный путь, нет прав на чтение или файл лежит не там, где вы ожидаете.
Для отладки можно временно записывать данные в лог. Это безопаснее, чем выводить их в браузер или ломать отправку письма var_dump-ом.
<?php
add_filter( 'woocommerce_email_attachments', 'wpmail_debug_email_attachments', 10, 4 );
function wpmail_debug_email_attachments( $attachments, $email_id, $order, $email ) {
if ( ! $order instanceof WC_Order ) {
error_log( 'WPMail: order object missing for email ' . $email_id );
return $attachments;
}
$file_path = WP_CONTENT_DIR . '/uploads/docs/warranty.pdf';
error_log( 'WPMail: email=' . $email_id . ' status=' . $order->get_status() . ' file=' . $file_path );
error_log( 'WPMail: exists=' . ( file_exists( $file_path ) ? 'yes' : 'no' ) . ' readable=' . ( is_readable( $file_path ) ? 'yes' : 'no' ) );
return $attachments;
}
После тестовой отправки откройте wp-content/debug.log, если у вас включен WP_DEBUG_LOG. Если лог показывает exists=no, сначала исправляйте путь к файлу. Если readable=no — проверьте права доступа на сервере.
Проверка результата после внедрения
Не ограничивайтесь одним тестовым заказом. Лучше проверить минимум два сценария: нужный статус и статус, для которого вложение не должно уходить. Иначе можно случайно отправлять документы не тем получателям.
- Создайте тестовый заказ в WooCommerce.
- Переведите его в нужный статус, например
processingилиcompleted. - Проверьте письмо в почтовом клиенте: есть ли вложение, правильный ли файл и не поврежден ли он.
- Повторите тест для статуса, который не должен добавлять файл.
- Если используете SMTP или почтовый API, проверьте, не режется ли письмо на стороне сервиса из-за размера вложения.
Полезно открыть исходник письма в почтовом клиенте и убедиться, что MIME-часть с вложением присутствует. Если файл есть в логике WordPress, но не доходит до ящика, проблема уже не в хуке, а в доставке или ограничениях почтового сервиса.
Частые ошибки и как их исправить
- Вложение добавляется во все письма. Причина: нет проверки
$email_idили статус заказа не фильтруется. Исправление: ограничьте и тип письма, и статус. - Файл не прикрепляется вообще. Причина: неверный абсолютный путь или файл недоступен для чтения. Исправление: используйте
WP_CONTENT_DIR,file_exists()иis_readable(). - Письмо уходит без ошибки, но вложение не видно. Причина: почтовый сервис режет вложения по размеру или клиент скрывает их в интерфейсе. Исправление: проверьте размер файла и отправку через другой ящик.
- Вложение появляется дважды. Причина: файл добавляет и код, и плагин. Исправление: оставьте один источник логики.
- Падает производительность на массовой отправке. Причина: файл собирается динамически или читается с медленного хранилища. Исправление: храните готовые документы локально и не генерируйте их на лету в фильтре отправки.
Безопасность и производительность: что не стоит делать
Не подставляйте путь к файлу из пользовательского ввода и не собирайте его из произвольных метаданных без проверки. Если заказчик может влиять на имя вложения, это уже зона риска: можно случайно открыть доступ к чужому файлу или попытаться прикрепить несуществующий путь.
Для чувствительных документов лучше хранить их в закрытой директории, а не в публичном каталоге без ограничений. Если файл должен быть доступен только после отправки письма, не делайте его угадываемым по URL.
Если у вас много заказов и тяжелые PDF, не генерируйте документ в момент отправки письма. Лучше подготовить файл заранее или вынести генерацию в отдельный процесс. Иначе отправка email станет узким местом в оформлении заказа.
Что выбрать: код, плагин или гибрид
| Подход | Когда подходит | Минус |
|---|---|---|
Код через woocommerce_email_attachments | Нужна точная привязка к статусам и типам писем | Требует поддержки в теме или mu-plugin |
| Плагин для PDF/документов | Нужно быстро без разработки | Часто добавляет вложения шире, чем нужно |
| Гибрид: плагин для генерации, код для фильтрации | Документы формирует плагин, а правила отправки задаете вы | Нужно следить за совместимостью |
Если у вас уже есть плагин для документов, не спешите от него отказываться. Часто достаточно отключить автоматическую отправку и оставить генерацию файла, а точку принятия решения перенести в код. Это дает контроль без полной переработки процесса.
Если вы хотите дальше чистить и упорядочивать email-логику магазина, удобно держать такие правки в отдельном mu-plugin, а не в functions.php. Тогда обновление темы не сломает отправку вложений и не затрет вашу логику.