В модулях до сих пор вызывают CEvent::Send «на удачу»: без проверки результата, с чужим LID и ожиданием, что письмо уйдёт внутри того же хита. Штатный D7-контракт — Bitrix\Main\Mail\Event: постановка в очередь через send или синхронная отправка через sendImmediate.
Рядом по теме: Result и Error, EventManager (это другие события), cron в Linux, Logger.
Если нужна помощь с модулем или отладкой почты на проде — услуги и контакты на karoche.ru.
Чем Mail\Event отличается от EventManager
Bitrix\Main\EventManager подписывает обработчики на события модулей (OnAfterUserAdd и т.п.). Bitrix\Main\Mail\Event работает с почтовыми событиями: тип из «Почтовые события», шаблон письма, поля #EMAIL#, очередь b_event.
Путаница в названии дорого стоит: подписались на ядерное событие и ждут письмо, либо наоборот вызывают Mail\Event там, где нужен хук бизнес-логики. Это два разных механизма.
Минимальный вызов Event::send
send создаёт запись почтового события и возвращает \Bitrix\Main\Entity\AddResult (аналог старого CEvent::Send). Фактическая отправка SMTP обычно происходит позже — агентом при обработке очереди.
<?php
use Bitrix\Main\Mail\Event;
use Bitrix\Main\Loader;
Loader::includeModule('main');
$result = Event::send([
'EVENT_NAME' => 'NEW_USER',
'LID' => 's1',
'C_FIELDS' => [
'EMAIL' => 'user@example.com',
'USER_ID' => 42,
],
]);
if (!$result->isSuccess()) {
// не попали в очередь / ошибка добавления
$errors = $result->getErrorMessages();
// залогировать через Diag\Logger или свой канал
}
Успешный isSuccess() значит: событие попало в очередь. Это ещё не «письмо в инбоксе». Если агенты крутятся только от хитов и ночью трафика нет, очередь копится часами.
Когда нужен sendImmediate
sendImmediate пытается отправить письмо в текущем запросе и не создаёт запись в b_event (по контракту legacy CEvent::SendImmediate; D7-метод — заявленный аналог с оговорками).
Типичные случаи:
- Код восстановления пароля / одноразовый код, где задержка очереди неприемлема.
- Фоновый CLI/агент, где вы уже «внутри крона» и не хотите второй слой очереди.
- Диагностика: проверить SMTP «здесь и сейчас», без ожидания агента.
<?php
use Bitrix\Main\Mail\Event;
$flag = Event::sendImmediate([
'EVENT_NAME' => 'USER_PASS_REQUEST',
'LID' => 's1',
'C_FIELDS' => [
'EMAIL' => 'user@example.com',
'CODE' => 'Af329',
],
]);
// строковые коды (как у CEvent::SendImmediate), не объект Result
if ($flag === Event::SEND_RESULT_SUCCESS) { // 'Y'
// ушло из PHP-процесса
} elseif ($flag === Event::SEND_RESULT_TEMPLATE_NOT_FOUND) { // '0'
// нет активного шаблона под EVENT_NAME + LID
} elseif ($flag === Event::SEND_RESULT_ERROR) { // 'F'
// сбой отправки
}
Константы результата (из документации CEvent::SendImmediate): SEND_RESULT_NONE = 'N', SUCCESS = 'Y', ERROR = 'F', PARTLY = 'P', TEMPLATE_NOT_FOUND = '0'.
Важная оговорка сообщества на странице документации: Bitrix\Main\Mail\Event::sendImmediate не вызывает обработчики OnBeforeEventAdd, тогда как CEvent::SendImmediate их выполняет. Если ваш код полагается на этот хук (подмена полей, стоп-лист) — проверьте поведение на стенде перед заменой вызова.
Чем отличаются send и sendImmediate
| Критерий | Event::send | Event::sendImmediate |
|---|---|---|
Очередь b_event | Да, запись создаётся | Нет (по контракту immediate) |
| Когда уходит SMTP | Позже, агентом / cron | В текущем хите/процессе |
| Тип возврата | Entity\AddResult (isSuccess, ошибки) | Строковый код Y/F/P/N/0 |
| Нагрузка на хит | Низкая (запись в БД) | Выше (сеть SMTP внутри запроса) |
| Типичный сценарий | Заказ, регистрация, фоновые уведомления | OTP, срочный алерт, отладка |
Для пользовательского HTTP-запроса по умолчанию берите send. sendImmediate в тяжёлом SMTP на каждом клике тормозит ответ и сильнее зависит от доступности почтового сервера.
LID, MESSAGE_ID, FILE и C_FIELDS
В D7 параметр LID — строка. Несколько сайтов передают через запятую ("s1,s2"), не массивом как в части старых вызовов CEvent.
Частая ловушка: в LID попадает языковой код или константа вроде ru, а шаблоны привязаны к сайту s1. Результат — TEMPLATE_NOT_FOUND / пустая отправка при живом шаблоне в админке.
Поля:
EVENT_NAME— символьный код типа почтового события.C_FIELDS— значения для макросов шаблона (#EMAIL#,#ORDER_ID#…). Ключи должны совпадать с полями типа события.MESSAGE_ID(int) — конкретный шаблон, если на тип повешено несколько.FILE— массив id файловCFileили абсолютных путей для вложений. Файл сначала сохраняют (CFile::SaveFile), потом передают id; временные файлы после отправки можно удалить.
<?php
use Bitrix\Main\Mail\Event;
$fileId = \CFile::SaveFile($_FILES['attach'], 'mailatt');
Event::send([
'EVENT_NAME' => 'LOCAL_FORM_FEEDBACK',
'LID' => 's1',
'MESSAGE_ID' => 64,
'C_FIELDS' => [
'EMAIL' => $email,
'MESSAGE' => $text,
],
'FILE' => $fileId ? [$fileId] : [],
]);
Пользовательский текст из формы не кладите в шаблон без очистки: HTML из комментария легко уедет в письмо как разметка.
Как проверить, что письмо реально ушло
- Проверить возврат вызова:
AddResultдляsend, строковый код дляsendImmediate. - В админке: Сервисы → Почтовые события → просмотр очереди/журнала. Нет записи после
send— проблема до SMTP (код, EVENT_NAME, права, исключения раньше вызова). - Статус в журнале: ожидает / отправлено / ошибка. Текст ошибки SMTP часто прямо указывает на порт, relay или отказ получателя.
- Режим агентов: на проде очередь должна крутиться системным cron (
cron_events.php/ аналог), не только «от хитов». Иначе ночные письма стоят до первого утреннего визита. База по crontab — в гайде про cron для PHP. - Если в
b_eventстатус «отправлено», а в ящике пусто — смотреть SPF/DKIM, спам, блокировку исходящего порта на хостинге. Это уже слой MTA, не APIMail\Event.
Для отладки своего модуля удобно писать исход вызова в Diag\Logger: EVENT_NAME, LID, isSuccess / код immediate, id записи если доступен.
Типичные ошибки
Опечатка в EVENT_NAME. Шаблон есть, код события в вызове другой на одну букву. Immediate вернёт TEMPLATE_NOT_FOUND (0); у send смотрите ошибки AddResult и отсутствие нормальной обработки шаблона в журнале.
Неверный LID. Классика с мультиязычностью и копипастой SITE_ID / языка. Сверьте идентификатор сайта в настройках сайта и привязку шаблона.
Ждут мгновенной доставки от send. Метод честно положил задачу в очередь. Без cron письмо «висит» до агента.
Не проверяют результат. Ошибка шаблона всплывает только когда клиент пишет в поддержку.
Путают с EventManager. Подписка на EventManager::getInstance()->addEventHandler(...) письмо сама не отправит — нужен явный Mail\Event::send*.
sendImmediate в каждом хите формы. Таймаут SMTP = долгий TTFB и ложные «сайт лежит». Для формы обратной связи обычно достаточно send + рабочий cron.
Вложение без SaveFile. В FILE передали путь из $_FILES['tmp_name'] без сохранения — на следующем шаге файла уже нет.
Смена API без проверки хуков. Переезд с CEvent::SendImmediate на D7 Event::sendImmediate может обойти OnBeforeEventAdd — см. оговорку выше.
Чеклист перед продом
- Тип почтового события и активный шаблон на нужном
LID. - В шаблоне есть все макросы, которые вы кладёте в
C_FIELDS(и наоборот — нет обязательных пустых). - Вызов через
Bitrix\Main\Mail\Event, результат проверяется. - Для обычных уведомлений —
send; для OTP/срочного — осознанныйsendImmediate. - На проде агенты на системном cron, лог крона пишется, тестовое письмо появляется в журнале событий.
- Исходящий SMTP/порт разрешён хостингом; после успешной отправки из журнала при необходимости проверены DNS (SPF/DKIM).
- Нет секретов в теле письма (токены с длинным TTL, пароли в открытом виде).
Если разбираете связку контроллер → сервис → письмо, смотрите также Engine\Controller и контракт Result.
Источники
- Event::send (D7)
- CEvent::SendImmediate (коды результата и замечание про
OnBeforeEventAdd) - CEvent::Send
- Mail\Event — индекс
Нужна помощь с модулем Битрикс или почтовыми событиями на проекте — karoche.ru.
