Как правильно организовать отправку почты при подтверждении элемента справочника в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке бизнес-логики часто возникает задача: уведомить ответственных пользователей по электронной почте, когда реквизит карточки справочника переходит в определенный статус (например, флаг Подтверждено принимает значение Истина). На первый взгляд кажется очевидным просто вставить вызов отправки почты в обработчик события записи. Однако такой подход чреват серьезными проблемами с производительностью и целостностью данных. Разберем, как реализовать этот функционал надежно и архитектурно правильно.

Почему нельзя отправлять письма синхронно в транзакции записи

Выясним причину, по которой прямая отправка почтовых сообщений в процедурах ПередЗаписью или ПриЗаписи (как в модуле объекта, так и в подписке на событие) категорически не рекомендуется:

  1. Зависание транзакции и блокировки: Обработчики записи выполняются внутри транзакции базы данных. Сетевое взаимодействие с почтовым SMTP-сервером может занять от нескольких секунд до минуты при медленном ответе или сетевых сбоях. Все это время открытая транзакция удерживает блокировки в СУБД, парализуя работу других пользователей с базой.
  2. Ложные уведомления при откате транзакции: Если письмо ушло на SMTP-сервер, а следом произошла ошибка платформы или сработал Отказ = Истина в другой подписке, транзакция откатится, и изменения в справочнике сохранены не будут. При этом письмо адресаты уже получат, что приведет к дезинформации.

Шаг 1. Отслеживание момента изменения реквизита

Рассмотрим, как отследить переход флага в состояние Истина именно в момент его установки, исключая повторные рассылки при каждой последующей перезаписи уже подтвержденной карточки. Для этого сравним текущее состояние реквизита в объекте со значением, которое записано в базе данных.

Сделать это можно как в процедуре ПередЗаписью модуля объекта, так и в подписке на событие ПередЗаписью. Подписка предпочтительнее, так как позволяет не снимать типовой объект с поддержки. Посмотрим на пример кода проверки:


// В обработчике подписки на событие ПередЗаписью
Если Источник.Подтверждено Тогда
    Если Источник.ЭтоНовый() Тогда
        // Новый элемент сразу создается подтвержденным
        Источник.ДополнительныеСвойства.Вставить("НеобходимоОтправитьУведомление", Истина);
    Иначе
        // Проверяем старое значение из базы данных через ссылку
        СтароеЗначение = Источник.Ссылка.Подтверждено;
        Если Не СтароеЗначение Тогда
            Источник.ДополнительныеСвойства.Вставить("НеобходимоОтправитьУведомление", Истина);
        КонецЕсли;
    КонецЕсли;
КонецЕсли;

Мы используем ДополнительныеСвойства объекта, чтобы передать признак необходимости рассылки дальше по цепочке — в событие фиксации записи.

Шаг 2. Формирование транзакционной очереди уведомлений

Разберем наиболее надежный способ интеграции — использование регистра-очереди. Создадим непериодический регистр сведений ОчередьУведомлений со следующей структурой:

  1. Измерения: ИдентификаторЗаписи (тип УникальныйИдентификатор) или ссылка на объект и дата создания.
  2. Ресурсы / Реквизиты: СсылкаНаОбъект (тип СправочникСсылка...), СтатусОбработки (перечисление: КОтправке, Отправлено, Ошибка), ТекстОшибки (строка), ДатаСоздания (Дата).

В подписке на событие ПриЗаписи справочника проверим наличие флага в ДополнительныеСвойства и создадим запись в регистре:


// В подписке на событие ПриЗаписи
Если Источник.ДополнительныеСвойства.Свойство("НеобходимоОтправитьУведомление") 
    И Источник.ДополнительныеСвойства.НеобходимоОтправитьУведомление Тогда

    НаборЗаписей = РегистрыСведений.ОчередьУведомлений.СоздатьНаборЗаписей();
    НаборЗаписей.Отбор.ИдентификаторЗаписи.Установить(Новый УникальныйИдентификатор);
    
    НоваяЗапись = НаборЗаписей.Добавить();
    НоваяЗапись.ИдентификаторЗаписи = Новый УникальныйИдентификатор;
    НоваяЗапись.СсылкаНаОбъект     = Источник.Ссылка;
    НоваяЗапись.ДатаСоздания       = ТекущаяДатаСеанса();
    НоваяЗапись.СтатусОбработки    = Перечисления.СтатусыОчереди.КОтправке;
    
    НаборЗаписей.Записать();

КонецЕсли;

Поскольку запись в регистр сведений происходит внутри транзакции записи справочника, при любой непредвиденной ошибке откатятся сразу оба действия, что гарантирует абсолютную согласованность данных.

Шаг 3. Асинхронная отправка писем по расписанию

Проанализируем процедуру отправки писем, которая будет выполняться через РегламентныеЗадания в фоновом режиме. Такой подход полностью изолирует пользователей от задержек почтовых протоколов.

При отправке почты средствами платформы 1С через объект ИнтернетПочта важно соблюдать правила оптимизации соединений. Не следует подключаться к почтовому серверу заново для каждой отдельной строки регистра. Откроем сессию один раз, отправим всю накопившуюся пачку писем и корректно закроем сессию.


Процедура ОтправитьОчередьУведомлений() Экспорт

    // Выбираем порцию записей, ожидающих отправки
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ ПЕРВЫЕ 50
        |	Очередь.ИдентификаторЗаписи КАК ИдентификаторЗаписи,
        |	Очередь.СсылкаНаОбъект КАК СсылкаНаОбъект
        |ИЗ
        |	РегистрСведений.ОчередьУведомлений КАК Очередь
        |ГДЕ
        |	Очередь.СтатусОбработки = ЗНАЧЕНИЕ(Перечисление.СтатусыОчереди.КОтправке)";
        
    Результат = Запрос.Выполнить();
    Если Результат.Пустой() Тогда
        Возврат;
    КонецЕсли;
    
    Выборка = Результат.Выбрать();
    
    // Настраиваем почтовый профиль
    Профиль = Новый ИнтернетПочтовыйПрофиль;
    Профиль.АдресСервераSMTP = "smtp.example.com";
    Профиль.ПортSMTP         = 465;
    Профиль.ПользовательSMTP = "robot@example.com";
    Профиль.ПарольSMTP       = "SecretPassword";
    Профиль.ВремяОжидания    = 30;
    Профиль.ИспользоватьSSLSMTP = Истина;
    
    Почта = Новый ИнтернетПочта;
    Попытка
        Почта.Подключиться(Профиль);
    Исключение
        ЗаписьЖурналаРегистрации("ОтправкаПочты", УровеньЖурналаРегистрации.Ошибка,,, ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
        Возврат;
    КонецПопытки;
    
    Пока Выборка.Следующий() Цикл
        
        Письмо = Новый ИнтернетПочтовоеСообщение;
        Письмо.Тема = "Карточка подтверждена: " + Строка(Выборка.СсылкаНаОбъект);
        Письмо.Отправитель = "robot@example.com";
        Письмо.Получатели.Добавить("manager@example.com");
        Письмо.Тексты.Добавить("Уведомляем, что карточка переведена в подтвержденное состояние.", ТипТекстаПочтовогоСообщения.ПростойТекст);
        
        Попытка
            Почта.Послать(Письмо);
            
            // Фиксируем успешную отправку
            ЗафиксироватьСтатусОчереди(Выборка.ИдентификаторЗаписи, Перечисления.СтатусыОчереди.Отправлено, "");
        Исключение
            ЗафиксироватьСтатусОчереди(Выборка.ИдентификаторЗаписи, Перечисления.СтатусыОчереди.Ошибка, ОписаниеОшибки());
        КонецПопытки;
        
    КонецЦикла;
    
    Почта.Отключиться();

КонецПроцедуры

Альтернативный вариант: Фоновые задания для срочных писем

Если регламентное задание с интервалом в 1 минуту не подходит по требованиям бизнеса и уведомление должно уходить немедленно, рассмотрим запуск отправки через объект ФоновыеЗадания. Запуск фонового задания следует производить после завершения транзакции (например, в обработчике формы ПослеЗаписи или через обработчик ожидания), передавая в фоновую процедуру ссылку на подтвержденный объект:


&НаКлиенте
Процедура ПослеЗаписи(ПараметрыЗаписи)
    Если ЭтотОбъект.Подтверждено Тогда
        ЗапуститьФоновоеУведомление(Объект.Ссылка);
    КонецЕсли;
КонецПроцедуры

&НаСервереБезКонтекста
Процедура ЗапуститьФоновоеУведомление(СсылкаНаОбъект)
    ПараметрыЗадания = Новый Массив;
    ПараметрыЗадания.Добавить(СсылкаНаОбъект);
    
    ФоновыеЗадания.Выполнить("ИнтеграцияПочта.ОтправитьУведомлениеПоОбъекту", ПараметрыЗадания);
КонецПроцедуры

Такой механизм позволяет отправить письмо за доли секунды без блокировки интерфейса пользователя и без задержки транзакции СУБД.

Резюме: При реализации бизнес-событий всегда разделяйте транзакцию фиксации данных и интеграционные сетевые вызовы. Подписка на событие идеальна для регистрации намерения в регистре-очереди, а регламентные или фоновые задания гарантируют стабильную и бесперебойную отправку сообщений.

← На главную