Часто перед разработчиками встает задача: необходимо автоматически уведомить сотрудников или рабочую группу о создании или проведении важного документа. Первое, что приходит на ум, — добавить код отправки письма прямо в обработчик проведения документа. Однако это является серьезной архитектурной ошибкой, которая может привести к замедлению работы пользователей, длительным блокировкам и потере данных. Давайте разберемся, почему так делать нельзя и какие есть правильные, надежные способы решения этой задачи.
Основная проблема заключается в том, что отправка письма — это синхронная сетевая операция. Когда ваш код обращается к почтовому серверу, система ждет от него ответа. Если сервер недоступен, медленно отвечает или есть проблемы с сетью, вся транзакция проведения документа "зависнет" в ожидании. Пользователь в это время не сможет работать, а таблицы базы данных будут заблокированы, мешая работе других сотрудников.
Более того, представьте ситуацию, когда кто-то запускает групповое перепроведение документов за месяц. Если в каждом документе будет прямой вызов отправки почты, это создаст лавинообразную нагрузку на почтовый сервер и, скорее всего, приведет к его отказу.
Поэтому ключевое правило: любое взаимодействие с внешними системами (почта, HTTP-сервисы и т.д.) должно выполняться асинхронно, то есть вне основной транзакции записи данных. Для решения подобных задач в высоконагруженных системах часто используют специальные конвейеры обработки задач, позволяющие избежать зависаний интерфейса.
Рассмотрим два основных правильных подхода к реализации этой задачи.
Это классический и самый надежный способ, который гарантирует, что ваше уведомление не потеряется, даже если почтовый сервер временно недоступен или произошел сбой в системе. Суть метода в том, чтобы разделить процесс на два этапа: быструю постановку задачи в очередь и последующую неспешную обработку этой очереди фоновым процессом. Вы можете добавить возможность отправки почтовых сообщений в вашу конфигурацию, используя расширения, чтобы не менять типовые механизмы — для этого подойдёт расширение для триггерных рассылок Email, Telegram и SMS в 1С.
Шаг 1. Создаем очередь сообщений
В качестве очереди будем использовать обычный Регистр сведений. Создадим его в конфигураторе, назовем, например, ОчередьОтправкиУведомлений. Структура может быть следующей:
ДокументИсточник (Тип: ДокументСсылка) - ссылка на документ, который инициировал отправку.Адресат (Строка) - email получателя или группы.Тема (Строка) - тема письма.ТекстПисьма (Строка неограниченной длины) - тело письма.Статус (ПеречислениеСсылка.СтатусыОтправки) - например, со значениями "КОтправке", "Отправлено", "Ошибка".КоличествоПопыток (Число) - счетчик попыток отправки.ТекстОшибки (Строка) - для записи информации об ошибке.Шаг 2. Добавляем задачу в очередь при проведении документа
Теперь в модуле объекта нужного нам документа, в процедуре ОбработкаПроведения, добавим код, который будет создавать запись в нашем новом регистре. Это быстрая операция, которая выполняется в той же транзакции, что и движения документа.
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
// ... ваш основной код проведения документа ...
// ... формирование движений ...
// Формируем задачу на отправку уведомления
МенеджерЗаписи = РегистрыСведений.ОчередьОтправкиУведомлений.СоздатьМенеджерЗаписи();
МенеджерЗаписи.ДокументИсточник = Ссылка;
// Заполняем данные для письма
МенеджерЗаписи.Адресат = "workgroup@example.com";
МенеджерЗаписи.Тема = "Создан новый документ: " + ЭтотОбъект;
МенеджерЗаписи.ТекстПисьма = "Добрый день! В системе создан и проведен документ "
+ ЭтотОбъект + ". Просьба ознакомиться.";
МенеджерЗаписи.Статус = Перечисления.СтатусыОтправки.КОтправке;
// Записываем задачу в очередь
МенеджерЗаписи.Записать();
КонецПроцедуры
Теперь, как только документ успешно проведется, в нашем регистре появится новая запись со статусом "КОтправке". Транзакция завершится, и пользователь сможет продолжить работу без задержек.
Шаг 3. Создаем обработчик очереди
Для обработки нашей очереди создадим Регламентное задание. Оно будет с заданной периодичностью (например, раз в 5 минут) запускать процедуру, которая проверит очередь и отправит все накопившиеся письма.
РегламентноеЗадание.УправлениеПочтой.ОтправитьУведомленияИзОчереди().Шаг 4. Пишем код для отправки писем
В общем модуле (например, УправлениеПочтой) создадим экспортную процедуру, которую будет вызывать наше регламентное задание. Если вы хотите сделать уведомления более информативными, можно отправить HTML таблицу на почту с данными из документа, а также настроить подпись в сообщениях, чтобы письма выглядели официально.
Процедура ОтправитьУведомленияИзОчереди() Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 10
| ОчередьОтправкиУведомлений.ДокументИсточник,
| ОчередьОтправкиУведомлений.Адресат,
| ОчередьОтправкиУведомлений.Тема,
| ОчередьОтправкиУведомлений.ТекстПисьма,
| ОчередьОтправкиУведомлений.КоличествоПопыток
|ИЗ
| РегистрСведений.ОчередьОтправкиУведомлений КАК ОчередьОтправкиУведомлений
|ГДЕ
| ОчередьОтправкиУведомлений.Статус = &СтатусКОтправке
| И ОчередьОтправкиУведомлений.КоличествоПопыток < 5"; // Ограничим число попыток
Запрос.УстановитьПараметр("СтатусКОтправке", Перечисления.СтатусыОтправки.КОтправке);
Выборка = Запрос.Выполнить().Выбрать();
// Получаем настройки системной учетной записи почты
УчетнаяЗапись = Справочники.УчетныеЗаписиЭлектроннойПочты.НайтиПоНаименованию("Системная");
Если НЕ ЗначениеЗаполнено(УчетнаяЗапись) Тогда
// Записать в журнал регистрации ошибку, что не найдена учетная запись
Возврат;
КонецЕсли;
ИнтернетПочта = Новый ИнтернетПочта;
Пока Выборка.Следующий() Цикл
Письмо = Новый ИнтернетПочтовоеСообщение;
Письмо.Получатели.Добавить(Выборка.Адресат);
Письмо.Тема = Выборка.Тема;
Письмо.Тексты.Добавить(Выборка.ТекстПисьма);
Попытка
// Используем стандартный механизм конфигурации для подключения
Профиль = УправлениеЭлектроннойПочтой.ПолучитьПрофильИнтернетПочты(УчетнаяЗапись);
ИнтернетПочта.Подключиться(Профиль);
ИнтернетПочта.Послать(Письмо);
ИнтернетПочта.Отключиться();
// Успех! Обновляем статус в регистре
МенеджерЗаписи = РегистрыСведений.ОчередьОтправкиУведомлений.СоздатьМенеджерЗаписи();
МенеджерЗаписи.ДокументИсточник = Выборка.ДокументИсточник;
МенеджерЗаписи.Прочитать(); // Важно прочитать, чтобы не затереть другие ресурсы
МенеджерЗаписи.Статус = Перечисления.СтатусыОтправки.Отправлено;
МенеджерЗаписи.Записать();
Исключение
// Ошибка! Обновляем статус и записываем ошибку
ОписаниеОшибки = ОписаниеОшибки();
МенеджерЗаписи = РегистрыСведений.ОчередьОтправкиУведомлений.СоздатьМенеджерЗаписи();
МенеджерЗаписи.ДокументИсточник = Выборка.ДокументИсточник;
МенеджерЗаписи.Прочитать();
МенеджерЗаписи.Статус = Перечисления.СтатусыОтправки.Ошибка;
МенеджерЗаписи.КоличествоПопыток = Выборка.КоличествоПопыток + 1;
МенеджерЗаписи.ТекстОшибки = ОписаниеОшибки;
МенеджерЗаписи.Записать();
КонецПопытки;
КонецЦикла;
КонецПроцедуры
Преимущества этого подхода: высочайшая надежность, устойчивость к сбоям, отсутствие влияния на работу пользователей. Недостаток: есть задержка между проведением документа и отправкой письма, равная интервалу запуска регламентного задания.
Этот метод позволяет запустить отправку почти мгновенно после проведения документа, но делает это в отдельном, неинтерактивном сеансе, не "подвешивая" интерфейс пользователя. Однако он менее надежен, чем метод с очередью.
Ключевое отличие: код запуска фонового задания нужно размещать не в модуле объекта, а в модуле формы документа. Это защитит систему от массовых рассылок при групповом перепроведении, так как в этом случае формы документов не открываются.
Шаг 1. Размещаем код запуска в форме документа
В форме документа найдем обработчик события, который выполняется после успешной записи, например, ПослеЗаписиНаСервере.
&НаСервере
Процедура ПослеЗаписиНаСервере(ТекущийОбъект, ПараметрыЗаписи)
// Проверяем, что документ проведен и это не отмена проведения
Если ПараметрыЗаписи.РежимЗаписи = РежимЗаписиДокумента.Проведение Тогда
// Готовим параметры для передачи в фоновое задание
ПараметрыВызова = Новый Массив;
ПараметрыВызова.Добавить(ТекущийОбъект.Ссылка);
ПараметрыВызова.Добавить("workgroup@example.com");
// Запускаем фоновое задание
ФоновыеЗадания.Выполнить("УправлениеПочтой.ОтправитьУведомлениеФоновымЗаданием", ПараметрыВызова);
КонецЕсли;
КонецПроцедуры
Шаг 2. Пишем код для выполнения в фоновом задании
В общем модуле УправлениеПочтой создадим еще одну экспортную процедуру, которую мы указали при вызове.
Процедура ОтправитьУведомлениеФоновымЗаданием(СсылкаНаДокумент, Адресат) Экспорт
ДокументОбъект = СсылкаНаДокумент.ПолучитьОбъект();
Тема = "Создан новый документ: " + ДокументОбъект;
ТекстПисьма = "Добрый день! В системе создан и проведен документ "
+ ДокументОбъект + ". Просьба ознакомиться.";
// Код отправки письма (аналогичен коду из регламентного задания)
Попытка
// ... настройка ИнтернетПочта, Письмо, подключение и отправка ...
// Успешная отправка, можно записать информацию в какой-нибудь лог
Исключение
// Ошибка, обязательно записываем в Журнал Регистрации
ТекстОшибки = "Не удалось отправить уведомление по документу "
+ СсылкаНаДокумент + ". " + ОписаниеОшибки();
ЗаписьЖурналаРегистрации("ОтправкаПочты.Ошибка", УровеньЖурналаРегистрации.Ошибка, , , ТекстОшибки);
КонецПопытки;
КонецПроцедуры
Преимущества: отправка происходит практически мгновенно. Недостаток: если сервер будет перезагружен или произойдет сбой платформы в момент между запуском фонового задания и его завершением, информация о необходимости отправки письма будет утеряна, так как она нигде не сохранена.
Независимо от выбранного способа, обязательно учтите следующие моменты:
1. Защита от отправки писем из тестовых баз
Это критически важный пункт, чтобы случайно не отправить реальные уведомления клиентам или сотрудникам из копии базы. Самый простой способ — завести константу.
РазрешитьОтправкуЭлектроннойПочты (тип Булево).Истина, а во всех копиях (тестовых, разработческих) — в Ложь.
Если НЕ Константы.РазрешитьОтправкуЭлектроннойПочты.Получить() Тогда
Возврат; // Не отправляем письма из этой базы
КонецЕсли;
// ... ваш код отправки ...
2. Использование штатного документа ЭлектронноеПисьмоИсходящее
Вместо прямого использования объекта ИнтернетПочта, рассмотрите возможность программного создания и отправки документа ЭлектронноеПисьмоИсходящее. Это дает большое преимущество: все отправленные письма сохраняются в системе, их можно просмотреть, проверить статус, переотправить вручную в случае ошибки. Это готовый механизм логирования и контроля.
3. Шаблонизация текста писем
Не "зашивайте" текст писем прямо в код. Используйте для этого Макеты. Создайте макет с текстом письма и специальными плейсхолдерами (например, [НомерДокумента], [ДатаДокумента]). В коде получайте текст из макета и заменяйте плейсхолдеры реальными данными. Это позволит изменять шаблоны уведомлений без привлечения программиста.