[{"data":1,"prerenderedAt":39},["ShallowReactive",2],{"article:bitrix-mail-events":3},{"html":4,"toc":5},"\u003Cp>Форма сохранила заявку, но уведомление менеджеру не пришло. Причина может находиться на разных этапах: событие не записалось в очередь, не нашёлся почтовый шаблон, обработчик очереди не запустился или транспорт не принял письмо. По одному результату вызова API эти ситуации не различить.\u003C\u002Fp>\n\u003Cp>Для обычных уведомлений в Битрикс используется \u003Ccode>\\Bitrix\\Main\\Mail\\Event::send()\u003C\u002Fcode>: метод ставит событие в очередь. \u003Ccode>\\Bitrix\\Main\\Mail\\Event::sendImmediate()\u003C\u002Fcode> обрабатывает его сразу, без записи в очередь. Разберу оба способа, настройку отправителя и проверку статусов, по которым можно найти место сбоя.\u003C\u002Fp>\n\u003Ch2 id=\"section-1\">Связать поля события с почтовым шаблоном\u003C\u002Fh2>\n\u003Cp>Почтовое событие передаёт данные, а шаблон определяет отправителя, получателя, тему и содержимое письма. В примерах используется тип события \u003Ccode>PROJECT_REQUEST_CREATED\u003C\u002Fcode> с полями \u003Ccode>EMAIL_TO\u003C\u002Fcode>, \u003Ccode>REQUEST_ID\u003C\u002Fcode> и \u003Ccode>CLIENT_NAME\u003C\u002Fcode>. Ему должен соответствовать активный шаблон, привязанный к сайту \u003Ccode>s1\u003C\u002Fcode>. Шаблон в текстовом формате:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">От кого: #DEFAULT_EMAIL_FROM#\nКому: #EMAIL_TO#\nТема: Заявка №#REQUEST_ID#\n\nПолучена заявка №#REQUEST_ID#.\nКлиент: #CLIENT_NAME#.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Значения для этих макросов передаются в \u003Ccode>C_FIELDS\u003C\u002Fcode>. \u003Ccode>DEFAULT_EMAIL_FROM\u003C\u002Fcode> заполняется штатно. Если шаблон привязан к другому сайту или неактивен, одного корректного вызова API недостаточно.\u003C\u002Fp>\n\u003Cp>Одному типу события могут соответствовать несколько шаблонов. При обычном выборе Битрикс обработает подходящие активные шаблоны для указанного сайта и языка. Проверьте их количество, если вместо одного уведомления уходят несколько писем.\u003C\u002Fp>\n\u003Ch2 id=\"section-2\">Поставить уведомление в очередь через Event::send()\u003C\u002Fh2>\n\u003Cp>Очередь подходит для уведомлений, результат которых не требуется получать прямо в пользовательском запросе. В обработчике после сохранения заявки:\u003C\u002Fp>\n\u003Cpre tabindex=\"0\" aria-label=\"Блок кода: php\">\u003Ccode class=\"hljs language-php\">\u003Cspan class=\"hljs-keyword\">use\u003C\u002Fspan> \u003Cspan class=\"hljs-title\">Bitrix\u003C\u002Fspan>\\\u003Cspan class=\"hljs-title\">Main\u003C\u002Fspan>\\\u003Cspan class=\"hljs-title\">Mail\u003C\u002Fspan>\\\u003Cspan class=\"hljs-title\">Event\u003C\u002Fspan>;\n\n\u003Cspan class=\"hljs-variable\">$result\u003C\u002Fspan> = \u003Cspan class=\"hljs-title class_\">Event\u003C\u002Fspan>::\u003Cspan class=\"hljs-title function_\">send\u003C\u002Fspan>([\n    \u003Cspan class=\"hljs-string\">'EVENT_NAME'\u003C\u002Fspan> =&gt; \u003Cspan class=\"hljs-string\">'PROJECT_REQUEST_CREATED'\u003C\u002Fspan>,\n    \u003Cspan class=\"hljs-string\">'LID'\u003C\u002Fspan> =&gt; \u003Cspan class=\"hljs-string\">'s1'\u003C\u002Fspan>,\n    \u003Cspan class=\"hljs-string\">'LANGUAGE_ID'\u003C\u002Fspan> =&gt; \u003Cspan class=\"hljs-string\">'ru'\u003C\u002Fspan>,\n    \u003Cspan class=\"hljs-string\">'DUPLICATE'\u003C\u002Fspan> =&gt; \u003Cspan class=\"hljs-string\">'N'\u003C\u002Fspan>,\n    \u003Cspan class=\"hljs-string\">'C_FIELDS'\u003C\u002Fspan> =&gt; [\n        \u003Cspan class=\"hljs-string\">'EMAIL_TO'\u003C\u002Fspan> =&gt; \u003Cspan class=\"hljs-string\">'manager@example.com'\u003C\u002Fspan>,\n        \u003Cspan class=\"hljs-string\">'REQUEST_ID'\u003C\u002Fspan> =&gt; \u003Cspan class=\"hljs-string\">'124'\u003C\u002Fspan>,\n        \u003Cspan class=\"hljs-string\">'CLIENT_NAME'\u003C\u002Fspan> =&gt; \u003Cspan class=\"hljs-string\">'Анна'\u003C\u002Fspan>,\n    ],\n]);\n\n\u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (!\u003Cspan class=\"hljs-variable\">$result\u003C\u002Fspan>-&gt;\u003Cspan class=\"hljs-title function_\">isSuccess\u003C\u002Fspan>()) {\n    \u003Cspan class=\"hljs-title function_\">error_log\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">'[mail-queue] '\u003C\u002Fspan> . \u003Cspan class=\"hljs-title function_\">implode\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">'; '\u003C\u002Fspan>, \u003Cspan class=\"hljs-variable\">$result\u003C\u002Fspan>-&gt;\u003Cspan class=\"hljs-title function_\">getErrorMessages\u003C\u002Fspan>()));\n} \u003Cspan class=\"hljs-keyword\">else\u003C\u002Fspan> {\n    \u003Cspan class=\"hljs-variable\">$eventId\u003C\u002Fspan> = (\u003Cspan class=\"hljs-keyword\">int\u003C\u002Fspan>)\u003Cspan class=\"hljs-variable\">$result\u003C\u002Fspan>-&gt;\u003Cspan class=\"hljs-title function_\">getId\u003C\u002Fspan>();\n    \u003Cspan class=\"hljs-title function_\">error_log\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">'[mail-queue] Создано почтовое событие ID='\u003C\u002Fspan> . \u003Cspan class=\"hljs-variable\">$eventId\u003C\u002Fspan>);\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Код выполняется в загруженном окружении Битрикс. Замените сайт, язык и адрес на значения своего проекта. Адрес менеджера берётся из настроек приложения; не позволяйте посетителю произвольно задавать получателя служебного уведомления. Если письмо адресовано самому пользователю, предварительно проверьте его email и условия отправки.\u003C\u002Fp>\n\u003Cp>\u003Ccode>isSuccess()\u003C\u002Fcode> сообщает об успешном добавлении события, а \u003Ccode>getId()\u003C\u002Fcode> возвращает ID записи в \u003Ccode>b_event\u003C\u002Fcode>. Это ещё не результат отправки: шаблоны будут обработаны позднее. Сохранённый ID позволяет найти конкретное событие, не ориентируясь только на время запроса.\u003C\u002Fp>\n\u003Cp>\u003Ccode>DUPLICATE =&gt; 'N'\u003C\u002Fcode> отключает отправку на штатный адрес дублирования писем. Этот параметр не предотвращает повторную постановку события и не ограничивает число подходящих шаблонов. Если обработчик заявки может сработать повторно, защиту от повторного уведомления нужно связать с состоянием самой заявки.\u003C\u002Fp>\n\u003Cp>Для выбора конкретного почтового шаблона существует \u003Ccode>MESSAGE_ID\u003C\u002Fcode>. Используйте ID действующего шаблона своего проекта и проверяйте его настройки: неверный ID не стоит считать гарантированным запретом отправки по другим шаблонам.\u003C\u002Fp>\n\u003Ch2 id=\"section-3\">Обработать событие сразу через Event::sendImmediate()\u003C\u002Fh2>\n\u003Cp>Немедленная обработка полезна для разовой диагностики шаблона и в сценарии, где результат вызова транспорта нужен до завершения текущего запроса. Она увеличивает время ответа и не исправляет неисправный SMTP или неверный шаблон.\u003C\u002Fp>\n\u003Cpre tabindex=\"0\" aria-label=\"Блок кода: php\">\u003Ccode class=\"hljs language-php\">\u003Cspan class=\"hljs-keyword\">use\u003C\u002Fspan> \u003Cspan class=\"hljs-title\">Bitrix\u003C\u002Fspan>\\\u003Cspan class=\"hljs-title\">Main\u003C\u002Fspan>\\\u003Cspan class=\"hljs-title\">Mail\u003C\u002Fspan>\\\u003Cspan class=\"hljs-title\">Event\u003C\u002Fspan>;\n\n\u003Cspan class=\"hljs-variable\">$status\u003C\u002Fspan> = \u003Cspan class=\"hljs-title class_\">Event\u003C\u002Fspan>::\u003Cspan class=\"hljs-title function_\">sendImmediate\u003C\u002Fspan>([\n    \u003Cspan class=\"hljs-string\">'EVENT_NAME'\u003C\u002Fspan> =&gt; \u003Cspan class=\"hljs-string\">'PROJECT_REQUEST_CREATED'\u003C\u002Fspan>,\n    \u003Cspan class=\"hljs-string\">'LID'\u003C\u002Fspan> =&gt; \u003Cspan class=\"hljs-string\">'s1'\u003C\u002Fspan>,\n    \u003Cspan class=\"hljs-string\">'LANGUAGE_ID'\u003C\u002Fspan> =&gt; \u003Cspan class=\"hljs-string\">'ru'\u003C\u002Fspan>,\n    \u003Cspan class=\"hljs-string\">'DUPLICATE'\u003C\u002Fspan> =&gt; \u003Cspan class=\"hljs-string\">'N'\u003C\u002Fspan>,\n    \u003Cspan class=\"hljs-string\">'C_FIELDS'\u003C\u002Fspan> =&gt; [\n        \u003Cspan class=\"hljs-string\">'EMAIL_TO'\u003C\u002Fspan> =&gt; \u003Cspan class=\"hljs-string\">'manager@example.com'\u003C\u002Fspan>,\n        \u003Cspan class=\"hljs-string\">'REQUEST_ID'\u003C\u002Fspan> =&gt; \u003Cspan class=\"hljs-string\">'124'\u003C\u002Fspan>,\n        \u003Cspan class=\"hljs-string\">'CLIENT_NAME'\u003C\u002Fspan> =&gt; \u003Cspan class=\"hljs-string\">'Анна'\u003C\u002Fspan>,\n    ],\n]);\n\n\u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (\u003Cspan class=\"hljs-variable\">$status\u003C\u002Fspan> === \u003Cspan class=\"hljs-title class_\">Event\u003C\u002Fspan>::\u003Cspan class=\"hljs-variable\">SEND_RESULT_SUCCESS\u003C\u002Fspan>) {\n    \u003Cspan class=\"hljs-title function_\">error_log\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">'[mail-immediate] Шаблоны обработаны, транспорт вернул успех'\u003C\u002Fspan>);\n} \u003Cspan class=\"hljs-keyword\">else\u003C\u002Fspan> {\n    \u003Cspan class=\"hljs-title function_\">error_log\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">'[mail-immediate] Результат обработки: '\u003C\u002Fspan> . \u003Cspan class=\"hljs-variable\">$status\u003C\u002Fspan>);\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Метод возвращает строковый статус, а не \u003Ccode>AddResult\u003C\u002Fcode>. Запись в \u003Ccode>b_event\u003C\u002Fcode> при таком вызове не создаётся, поэтому искать результат в очереди бессмысленно. Сравнивайте статус явно с константой: проверка \u003Ccode>if ($status)\u003C\u002Fcode> примет непустую строку \u003Ccode>F\u003C\u002Fcode> за истину.\u003C\u002Fp>\n\u003Cp>Код \u003Ccode>Y\u003C\u002Fcode> означает успешный результат отправки по обработанным шаблонам на уровне используемого транспорта. Он не подтверждает, что письмо попало во входящие получателя. После принятия письма возможны отказ принимающего сервера, фильтрация или попадание в спам.\u003C\u002Fp>\n\u003Cp>\u003Ccode>Event::sendImmediate()\u003C\u002Fcode> вызывает \u003Ccode>OnBeforeEventAdd\u003C\u002Fcode> до выбора и обработки шаблонов. Если обработчик возвращает \u003Ccode>false\u003C\u002Fcode>, метод сразу возвращает \u003Ccode>Event::SEND_RESULT_NONE\u003C\u002Fcode> (\u003Ccode>N\u003C\u002Fcode>). Записи в очереди при этом нет: название события \u003Ccode>OnBeforeEventAdd\u003C\u002Fcode> не означает, что оно вызывается только перед записью в \u003Ccode>b_event\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch2 id=\"section-4\">Выбрать адрес отправителя: DEFAULT_EMAIL_FROM\u003C\u002Fh2>\n\u003Cp>\u003Ccode>#DEFAULT_EMAIL_FROM#\u003C\u002Fcode> — макрос почтового шаблона, а не PHP-константа, которую нужно объявлять в \u003Ccode>init.php\u003C\u002Fcode>. Битрикс берёт адрес из поля \u003Ccode>EMAIL\u003C\u002Fcode> выбранного сайта. Если оно пустое, используется настройка главного модуля \u003Ccode>email_from\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>Проверить оба значения в PHP-коде с загруженным ядром:\u003C\u002Fp>\n\u003Cpre tabindex=\"0\" aria-label=\"Блок кода: php\">\u003Ccode class=\"hljs language-php\">\u003Cspan class=\"hljs-variable\">$site\u003C\u002Fspan> = \u003Cspan class=\"hljs-title class_\">\\Bitrix\\Main\\SiteTable\u003C\u002Fspan>::\u003Cspan class=\"hljs-title function_\">getById\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">'s1'\u003C\u002Fspan>)-&gt;\u003Cspan class=\"hljs-title function_\">fetch\u003C\u002Fspan>();\n\u003Cspan class=\"hljs-variable\">$siteFrom\u003C\u002Fspan> = \u003Cspan class=\"hljs-title function_\">trim\u003C\u002Fspan>((\u003Cspan class=\"hljs-keyword\">string\u003C\u002Fspan>)(\u003Cspan class=\"hljs-variable\">$site\u003C\u002Fspan>[\u003Cspan class=\"hljs-string\">'EMAIL'\u003C\u002Fspan>] ?? \u003Cspan class=\"hljs-string\">''\u003C\u002Fspan>));\n\u003Cspan class=\"hljs-variable\">$moduleFrom\u003C\u002Fspan> = \u003Cspan class=\"hljs-title class_\">\\Bitrix\\Main\\Config\\Option\u003C\u002Fspan>::\u003Cspan class=\"hljs-title function_\">get\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">'main'\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">'email_from'\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">''\u003C\u002Fspan>);\n\n\u003Cspan class=\"hljs-title function_\">var_export\u003C\u002Fspan>([\n    \u003Cspan class=\"hljs-string\">'site_email'\u003C\u002Fspan> =&gt; \u003Cspan class=\"hljs-variable\">$siteFrom\u003C\u002Fspan>,\n    \u003Cspan class=\"hljs-string\">'main_email_from'\u003C\u002Fspan> =&gt; \u003Cspan class=\"hljs-variable\">$moduleFrom\u003C\u002Fspan>,\n]);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Проверьте также \u003Ccode>EMAIL_FROM\u003C\u002Fcode> самого шаблона: если там указан буквальный адрес, изменение \u003Ccode>DEFAULT_EMAIL_FROM\u003C\u002Fcode> его не заменит. Используйте отправителя, разрешённого вашим почтовым сервисом. Передача email посетителя в \u003Ccode>From\u003C\u002Fcode> может конфликтовать с настройками сервиса и домена; для ответа посетителю предназначен \u003Ccode>Reply-To\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch2 id=\"section-5\">Найти событие и прочитать SUCCESS_EXEC в b_event\u003C\u002Fh2>\n\u003Cp>Очередь хранится в таблице \u003Ccode>b_event\u003C\u002Fcode>, в единственном числе. Запрос для конкретного ID события:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-sql\">SELECT ID, EVENT_NAME, LID, MESSAGE_ID,\n       DATE_INSERT, DATE_EXEC, SUCCESS_EXEC\nFROM b_event\nWHERE ID = 124;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Замените \u003Ccode>124\u003C\u002Fcode> на ID из результата \u003Ccode>Event::send()\u003C\u002Fcode>. Не путайте его с номером заявки, который передаётся внутри \u003Ccode>C_FIELDS\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>Основные статусы:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>\u003Ccode>N\u003C\u002Fcode>\u003C\u002Fstrong> — событие остаётся в очереди. Новая запись получает этот статус до обработки; длительное ожидание требует проверки запуска очереди. Обработчики также могут намеренно отложить событие.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>\u003Ccode>Y\u003C\u002Fcode>\u003C\u002Fstrong> — отправка по обработанным шаблонам вернула успех. Это не отчёт о доставке получателю.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>\u003Ccode>F\u003C\u002Fcode>\u003C\u002Fstrong> — обработанные шаблоны завершились ошибкой. Причина может быть при формировании письма или в транспорте, а не только в PHP \u003Ccode>mail()\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>\u003Ccode>P\u003C\u002Fcode>\u003C\u002Fstrong> — часть попыток по шаблонам успешна, часть завершилась ошибкой. Проверяйте каждый подходящий шаблон и его адреса.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>\u003Ccode>0\u003C\u002Fcode>\u003C\u002Fstrong> — отправка по шаблонам не состоялась. Сначала проверяйте их активность и привязку к событию и сайту; такой результат возможен также при пропуске шаблонов обработчиком.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>\u003Ccode>E\u003C\u002Fcode>\u003C\u002Fstrong> — обработка события прервана исключением. Ищите запись в журнале исключений Битрикс.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>\u003Ccode>DATE_EXEC\u003C\u002Fcode> помогает отличить свежую запись от уже обработанной. Не меняйте статус вручную на \u003Ccode>Y\u003C\u002Fcode>: это только скроет проблему. Возврат старых событий в \u003Ccode>N\u003C\u002Fcode> может вызвать повторные уведомления.\u003C\u002Fp>\n\u003Cp>Для оценки накопления очереди:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-sql\">SELECT EVENT_NAME, COUNT(*) AS PENDING,\n       MIN(DATE_INSERT) AS OLDEST\nFROM b_event\nWHERE SUCCESS_EXEC = 'N'\nGROUP BY EVENT_NAME\nORDER BY OLDEST;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Если события разных типов копятся одновременно, сначала проверяйте обработчик очереди. Если проблема только у одного типа, сравните его шаблоны и обработчики с работающими событиями.\u003C\u002Fp>\n\u003Ch2 id=\"section-6\">Разобраться с N: обработка на хитах или по cron\u003C\u002Fh2>\n\u003Cp>\u003Ccode>CEvent::CheckEvents()\u003C\u002Fcode> запускает проверку почтовой очереди. При \u003Ccode>BX_CRONTAB_SUPPORT === true\u003C\u002Fcode> обычный запрос без \u003Ccode>BX_CRONTAB === true\u003C\u002Fcode> пропускает эту проверку: предполагается запуск из cron. \u003Ccode>DisableEventsCheck === true\u003C\u002Fcode> также запрещает проверку.\u003C\u002Fp>\n\u003Cp>Поэтому удаление cron-констант — не универсальное исправление. Сначала определите, где очередь должна обрабатываться, и проверьте реальный запуск этого процесса. Состояние констант в текущем окружении можно посмотреть так:\u003C\u002Fp>\n\u003Cpre tabindex=\"0\" aria-label=\"Блок кода: php\">\u003Ccode class=\"hljs language-php\">\u003Cspan class=\"hljs-keyword\">foreach\u003C\u002Fspan> ([\u003Cspan class=\"hljs-string\">'BX_CRONTAB_SUPPORT'\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">'BX_CRONTAB'\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">'DisableEventsCheck'\u003C\u002Fspan>] \u003Cspan class=\"hljs-keyword\">as\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">$name\u003C\u002Fspan>) {\n    \u003Cspan class=\"hljs-title function_\">printf\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">\"%s = %s\\n\"\u003C\u002Fspan>, \u003Cspan class=\"hljs-variable\">$name\u003C\u002Fspan>,\n        \u003Cspan class=\"hljs-title function_\">defined\u003C\u002Fspan>(\u003Cspan class=\"hljs-variable\">$name\u003C\u002Fspan>) ? \u003Cspan class=\"hljs-title function_\">var_export\u003C\u002Fspan>(\u003Cspan class=\"hljs-title function_\">constant\u003C\u002Fspan>(\u003Cspan class=\"hljs-variable\">$name\u003C\u002Fspan>), \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>) : \u003Cspan class=\"hljs-string\">'не определена'\u003C\u002Fspan>);\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Веб-запрос и CLI могут загружать разные настройки. Проверьте задачу пользователя, от которого запускается сайт, путь к PHP, рабочий каталог и журнал выполнения. Убедитесь, что \u003Ccode>init.php\u003C\u002Fcode> не делает в CLI редирект и не рассчитывает на заполненные \u003Ccode>HTTP_HOST\u003C\u002Fcode> или другие данные браузерного запроса.\u003C\u002Fp>\n\u003Cp>Для отдельного обработчика почты можно использовать \u003Ccode>\u002Flocal\u002Fcli\u002Fprocess-mail.php\u003C\u002Fcode>:\u003C\u002Fp>\n\u003Cpre tabindex=\"0\" aria-label=\"Блок кода: php\">\u003Ccode class=\"hljs language-php\">\u003Cspan class=\"hljs-meta\">&lt;?php\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (PHP_SAPI !== \u003Cspan class=\"hljs-string\">'cli'\u003C\u002Fspan>) { \u003Cspan class=\"hljs-keyword\">exit\u003C\u002Fspan>; }\n\n\u003Cspan class=\"hljs-variable\">$_SERVER\u003C\u002Fspan>[\u003Cspan class=\"hljs-string\">'DOCUMENT_ROOT'\u003C\u002Fspan>] = \u003Cspan class=\"hljs-title function_\">dirname\u003C\u002Fspan>(\u003Cspan class=\"hljs-keyword\">__DIR__\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan>);\n\u003Cspan class=\"hljs-title function_\">define\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">'NO_KEEP_STATISTIC'\u003C\u002Fspan>, \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>);\n\u003Cspan class=\"hljs-title function_\">define\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">'BX_CRONTAB'\u003C\u002Fspan>, \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>);\n\n\u003Cspan class=\"hljs-keyword\">require\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">$_SERVER\u003C\u002Fspan>[\u003Cspan class=\"hljs-string\">'DOCUMENT_ROOT'\u003C\u002Fspan>] . \u003Cspan class=\"hljs-string\">'\u002Fbitrix\u002Fmodules\u002Fmain\u002Finclude\u002Fprolog_before.php'\u003C\u002Fspan>;\n\n\u003Cspan class=\"hljs-title class_\">\\CEvent\u003C\u002Fspan>::\u003Cspan class=\"hljs-title function_\">CheckEvents\u003C\u002Fspan>();\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>BX_CRONTAB\u003C\u002Fcode> здесь задаётся только для процесса обработки. Если в проекте уже есть рабочий обработчик очереди, исправьте его запуск, а не добавляйте второй независимо от первого.\u003C\u002Fp>\n\u003Cp>Разовый запуск выполняется от того же пользователя, которому доступны файлы и конфигурация сайта. Например, при корне сайта \u003Ccode>\u002Fvar\u002Fwww\u002Fsite\u003C\u002Fcode> и пользователе \u003Ccode>www-data\u003C\u002Fcode>:\u003C\u002Fp>\n\u003Cpre tabindex=\"0\" aria-label=\"Блок кода: bash\">\u003Ccode class=\"hljs language-bash\">\u003Cspan class=\"hljs-built_in\">sudo\u003C\u002Fspan> -u www-data \u002Fusr\u002Fbin\u002Fphp \u002Fvar\u002Fwww\u002Fsite\u002Flocal\u002Fcli\u002Fprocess-mail.php\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Команда действительно обрабатывает ожидающие письма, а не просто проверяет настройки. Запускайте её, когда готовы отправить накопившиеся уведомления. Битрикс берёт ограниченную порцию очереди за проход; один вызов не обязан обработать все записи. После запуска сравните статусы выбранных ID и возраст оставшейся очереди.\u003C\u002Fp>\n\u003Cp>Если ручной запуск работает, а плановый — нет, причина находится в расписании или окружении cron. Если статусы остаются \u003Ccode>N\u003C\u002Fcode>, проверьте запрещающие константы, ошибки выполнения и блокировку обработчика. Ещё одна возможная причина пропуска проверки — маркер пустой очереди в managed cache.\u003C\u002Fp>\n\u003Ch2 id=\"section-7\">Как managed cache влияет на проверку очереди\u003C\u002Fh2>\n\u003Cp>Битрикс кеширует не сами письма, а признак того, что ожидающих событий нет. Он хранится под ключом \u003Ccode>events\u003C\u002Fcode> и позволяет не обращаться к \u003Ccode>b_event\u003C\u002Fcode> при каждом запуске проверки.\u003C\u002Fp>\n\u003Cp>Последовательность такая:\u003C\u002Fp>\n\u003Col>\n\u003Cli>Обработчик ищет записи с \u003Ccode>SUCCESS_EXEC = 'N'\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>Если записей нет и кеширование очереди включено, он сохраняет маркер \u003Ccode>events\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>При следующем вызове \u003Ccode>CEvent::CheckEvents()\u003C\u002Fcode> наличие актуального маркера позволяет завершить проверку без выборки писем из базы.\u003C\u002Fli>\n\u003Cli>При добавлении нового события \u003Ccode>Event::send()\u003C\u002Fcode> сбрасывает этот маркер, чтобы очередь снова проверялась.\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>За использование маркера отвечает \u003Ccode>CACHED_b_event\u003C\u002Fcode>: значение \u003Ccode>false\u003C\u002Fcode> отключает эту проверку кеша. При штатном добавлении событий через API вручную очищать кеш после каждой отправки не требуется.\u003C\u002Fp>\n\u003Cp>Рассматривать кеш как причину стоит при конкретном расхождении: в \u003Ccode>b_event\u003C\u002Fcode> есть ожидающие записи, запуск обработчика подтверждён, запрещающих констант нет, но до выборки очереди выполнение не доходит. Например, прямое добавление записей SQL-запросом обходит сброс маркера, который делает \u003Ccode>Event::send()\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>Для проверки этой гипотезы сбросьте только маркер очереди в окружении Битрикс:\u003C\u002Fp>\n\u003Cpre tabindex=\"0\" aria-label=\"Блок кода: php\">\u003Ccode class=\"hljs language-php\">\u003Cspan class=\"hljs-title class_\">\\Bitrix\\Main\\Application\u003C\u002Fspan>::\u003Cspan class=\"hljs-title function_\">getInstance\u003C\u002Fspan>()-&gt;\u003Cspan class=\"hljs-title function_\">getManagedCache\u003C\u002Fspan>()-&gt;\u003Cspan class=\"hljs-title function_\">clean\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">'events'\u003C\u002Fspan>);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Затем повторите запуск обработчика и сравните статусы тех же событий. Очистка маркера сама письма не отправляет; следующий проход очереди уже может их обработать. Если поведение не изменилось, причина не подтверждена: вернитесь к запуску процесса, его ошибкам и блокировке.\u003C\u002Fp>\n\u003Cp>Если сброс помогает, но проблема возвращается, проверьте путь создания событий и ошибки доступа к хранилищу managed cache от пользователя PHP или cron. Очистка всего каталога \u003Ccode>\u002Fbitrix\u002Fmanaged_cache\u002F\u003C\u002Fcode> затронет другие данные и не нужна для проверки именно этого маркера.\u003C\u002Fp>\n\u003Ch2 id=\"section-8\">Разобраться с 0: шаблон не выбран или пропущен\u003C\u002Fh2>\n\u003Cp>Начните с сочетания \u003Ccode>EVENT_NAME\u003C\u002Fcode>, \u003Ccode>LID\u003C\u002Fcode> и \u003Ccode>LANGUAGE_ID\u003C\u002Fcode> из вызова. Затем проверьте, что нужный шаблон активен, относится к этому типу события и привязан к сайту. Если передаётся \u003Ccode>MESSAGE_ID\u003C\u002Fcode>, проверьте существование и активность именно этого шаблона.\u003C\u002Fp>\n\u003Cp>Просмотрите \u003Ccode>OnBeforeEventAdd\u003C\u002Fcode> и \u003Ccode>OnBeforeEventSend\u003C\u002Fcode>. Первый может отменить создание или немедленную обработку события, второй — пропустить отдельный шаблон. Если все шаблоны пропущены, результат может быть \u003Ccode>0\u003C\u002Fcode> даже при наличии активных шаблонов в базе.\u003C\u002Fp>\n\u003Cp>Ошибка SMTP не объясняет отсутствие выбранного шаблона: до транспорта выполнение могло вообще не дойти. Аналогично пропущенный макрос в тексте письма сам по себе не означает статус \u003Ccode>0\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch2 id=\"section-9\">Разобраться с F или P: поля шаблона и почтовый транспорт\u003C\u002Fh2>\n\u003Cp>Сначала проверьте подставленные адреса отправителя, получателя и копий. Макрос должен получать значение из \u003Ccode>C_FIELDS\u003C\u002Fcode> или штатных полей. Произвольный \u003Ccode>#BCC#\u003C\u002Fcode> не заполняется автоматически только потому, что он добавлен в шаблон: всё зависит от данных конкретного события. Ошибка в необязательной строке тела и пустой адрес получателя имеют разные последствия.\u003C\u002Fp>\n\u003Cp>Следующий этап — транспорт. Путь отправки проходит через \u003Ccode>bxmail()\u003C\u002Fcode> из \u003Ccode>\u002Fbitrix\u002Fmodules\u002Fmain\u002Ftools.php\u003C\u002Fcode>. Ядро может использовать встроенный SMTP, функцию \u003Ccode>custom_mail()\u003C\u002Fcode> или PHP \u003Ccode>mail()\u003C\u002Fcode>. При выбранном встроенном SMTP наличие \u003Ccode>custom_mail()\u003C\u002Fcode> не означает, что вызов дойдёт до неё. Сам файл \u003Ccode>tools.php\u003C\u002Fcode> для настройки почты не редактируют.\u003C\u002Fp>\n\u003Cp>Если проект определяет \u003Ccode>custom_mail()\u003C\u002Fcode>, проверьте её возвращаемое значение. Обёртка должна вернуть результат реальной передачи письма. Отсутствующий \u003Ccode>return\u003C\u002Fcode> может дать Битрикс неуспех после фактической отправки, а безусловный \u003Ccode>return true\u003C\u002Fcode> — скрыть сбой. Прямой вызов PHPMailer проверяет только настроенный для него SMTP-маршрут: он не проверяет почтовый шаблон, очередь и маршрут, выбранный Битрикс.\u003C\u002Fp>\n\u003Cp>При ошибке транспорта ищите конкретный ответ: отказ авторизации, недопустимый отправитель, ошибка TLS, недоступность сервера или превышение лимита. Путь к журналу зависит от используемой службы и окружения. \u003Ccode>\u002Fhome\u002Fbitrix\u002Fmsmtp_default.log\u003C\u002Fcode> относится к конфигурациям с соответствующим msmtp и не является общим журналом любой установки Битрикс.\u003C\u002Fp>\n\u003Cp>Проверяйте доступ к SMTP-конфигурации от пользователя реального процесса. Не меняйте владельца файлов на \u003Ccode>bitrix:bitrix\u003C\u002Fcode> автоматически, если PHP или cron работают от другого пользователя. Пароли и полные тела писем не должны попадать в публичный каталог сайта или вывод страницы при отладке.\u003C\u002Fp>\n\u003Cp>Если статус \u003Ccode>Y\u003C\u002Fcode>, но письма нет, переходите к журналам почтового сервиса: ищите конкретную отправку, отказ принимающей стороны, ограничения и фильтрацию. Проверьте также папку спама. Повторные вызовы \u003Ccode>Event::send()\u003C\u002Fcode> не исправляют доставляемость и создают новые уведомления.\u003C\u002Fp>\n\u003Ch2 id=\"section-10\">Проверить достижение транспорта через OnBeforePhpMail\u003C\u002Fh2>\n\u003Cp>\u003Ccode>OnBeforePhpMail\u003C\u002Fcode> вызывается внутри \u003Ccode>bxmail()\u003C\u002Fcode> перед выбором транспорта. Это D7-событие: обработчик получает объект \u003Ccode>\\Bitrix\\Main\\Event\u003C\u002Fcode>, а аргументы письма находятся в параметре \u003Ccode>arguments\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>Для временной проверки в \u003Ccode>\u002Flocal\u002Fphp_interface\u002Finit.php\u003C\u002Fcode>:\u003C\u002Fp>\n\u003Cpre tabindex=\"0\" aria-label=\"Блок кода: php\">\u003Ccode class=\"hljs language-php\">\u003Cspan class=\"hljs-title class_\">\\Bitrix\\Main\\EventManager\u003C\u002Fspan>::\u003Cspan class=\"hljs-title function_\">getInstance\u003C\u002Fspan>()-&gt;\u003Cspan class=\"hljs-title function_\">addEventHandler\u003C\u002Fspan>(\n    \u003Cspan class=\"hljs-string\">'main'\u003C\u002Fspan>,\n    \u003Cspan class=\"hljs-string\">'OnBeforePhpMail'\u003C\u002Fspan>,\n    \u003Cspan class=\"hljs-built_in\">static\u003C\u002Fspan> function (\\Bitrix\\Main\\Event \u003Cspan class=\"hljs-variable\">$event\u003C\u002Fspan>): \u003Cspan class=\"hljs-keyword\">void\u003C\u002Fspan> {\n        \u003Cspan class=\"hljs-variable\">$arguments\u003C\u002Fspan> = \u003Cspan class=\"hljs-variable\">$event\u003C\u002Fspan>-&gt;\u003Cspan class=\"hljs-title function_\">getParameter\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">'arguments'\u003C\u002Fspan>);\n        \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (!\u003Cspan class=\"hljs-title function_\">is_object\u003C\u002Fspan>(\u003Cspan class=\"hljs-variable\">$arguments\u003C\u002Fspan>)) { \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan>; }\n\n        \u003Cspan class=\"hljs-title function_\">error_log\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">'[mail-transport] Вызван bxmail; окружение='\u003C\u002Fspan> . PHP_SAPI);\n    }\n);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Запись подтверждает, что выполнение дошло до \u003Ccode>bxmail()\u003C\u002Fcode>. Она не содержит результата следующего вызова транспорта и не доказывает отправку. Обработчик срабатывает для всех писем, проходящих через эту функцию; после диагностики его следует убрать.\u003C\u002Fp>\n\u003Cp>Через объект \u003Ccode>arguments\u003C\u002Fcode> можно менять параметры письма, но это не обработчик типа почтового события. Для изменения полей конкретного события или выбора шаблона удобнее \u003Ccode>OnBeforeEventSend\u003C\u002Fcode>. Не пытайтесь отменить отправку простым \u003Ccode>return false\u003C\u002Fcode> из \u003Ccode>OnBeforePhpMail\u003C\u002Fcode>: \u003Ccode>bxmail()\u003C\u002Fcode> не использует этот результат как команду остановки.\u003C\u002Fp>\n\u003Ch2 id=\"section-11\">Что проверять при NULL и ошибках окружения\u003C\u002Fh2>\n\u003Cp>\u003Ccode>NULL\u003C\u002Fcode> в \u003Ccode>SUCCESS_EXEC\u003C\u002Fcode> не относится к штатным результатам обработки. Такая запись не попадёт в выборку \u003Ccode>SUCCESS_EXEC = 'N'\u003C\u002Fcode>. Проверьте код, который её создал, обновления базы и соответствие структуры таблицы установленному ядру.\u003C\u002Fp>\n\u003Cp>Начинать с \u003Ccode>ALTER TABLE\u003C\u002Fcode> или массовой замены \u003Ccode>NULL\u003C\u002Fcode> на \u003Ccode>N\u003C\u002Fcode> нельзя: изменение значения по умолчанию не объясняет появление старых записей, а повторная постановка может отправить уже обработанные уведомления. Исправление схемы имеет смысл только после подтверждения конкретного расхождения и определения судьбы существующих данных.\u003C\u002Fp>\n\u003Cp>Для проверки исправления создайте одно событие на контролируемый адрес и сохраните его ID. Убедитесь, что очередь его обработала, транспорт принял письмо, а получатель действительно его получил. Так у каждого этапа будет собственное подтверждение — от записи в \u003Ccode>b_event\u003C\u002Fcode> до доставки.\u003C\u002Fp>\n",[6,9,12,15,18,21,24,27,30,33,36],{"id":7,"title":8},"section-1","Связать поля события с почтовым шаблоном",{"id":10,"title":11},"section-2","Поставить уведомление в очередь через Event::send()",{"id":13,"title":14},"section-3","Обработать событие сразу через Event::sendImmediate()",{"id":16,"title":17},"section-4","Выбрать адрес отправителя: DEFAULT_EMAIL_FROM",{"id":19,"title":20},"section-5","Найти событие и прочитать SUCCESS_EXEC в b_event",{"id":22,"title":23},"section-6","Разобраться с N: обработка на хитах или по cron",{"id":25,"title":26},"section-7","Как managed cache влияет на проверку очереди",{"id":28,"title":29},"section-8","Разобраться с 0: шаблон не выбран или пропущен",{"id":31,"title":32},"section-9","Разобраться с F или P: поля шаблона и почтовый транспорт",{"id":34,"title":35},"section-10","Проверить достижение транспорта через OnBeforePhpMail",{"id":37,"title":38},"section-11","Что проверять при NULL и ошибках окружения",1790082845805]