При разработке функционала для регулярного информирования клиентов, дилеров или контрагентов (например, отправка актов сверки, отчетов о продажах, информации о задолженностях) разработчики сталкиваются с задачей организации массовой отправки писем. В нетиповых конфигурациях или при доработке существующих систем важно не просто сформировать печатную форму и отправить ее в цикле, но и обеспечить отказоустойчивость, соблюдение лимитов почтовых серверов и высокую производительность.
Разберем основные архитектурные подходы и технические нюансы реализации массовой рассылки отчетов в 1С:Предприятие 8.3.
Если в конфигурацию уже интегрирована или планируется к внедрению Библиотека стандартных подсистем (версии 2.1 и выше), наиболее надежным и функциональным решением будет использование готовой подсистемы «Рассылка отчетов».
Проанализируем преимущества этого подхода:
Если отчет уже разработан с использованием вариантов отчетов БСП, настройка рассылки сводится к созданию элемента в справочнике рассылок без написания дополнительного программного кода.
Прямая отправка сотен писем по нажатию интерактивной кнопки в пользовательском интерфейсе является неоптимальной. При сбоях интернет-соединения, разрыве SMTP-сессии или долгой генерации отчетов пользовательский сеанс зависнет, а часть писем останется неотправленной. Разберем по шагам, как построить надежную архитектуру через очередь сообщений.
Шаг 1. Создание структуры для хранения очереди
Создадим регистр сведений или специализированный справочник ОчередьИсходящейПочты со следующими реквизитами:
Контрагент (Ссылка на контрагента).АдресПолучателя (Строка с email-адресом).Тема и ТелоПисьма (Строки).ФайлВложения (Хранилище значения для двоичных данных сформированного отчета).Статус (Перечисление: «В очереди», «Отправлено», «Ошибка»).КоличествоПопыток (Число) и ТекстОшибки (Строка).ДатаСоздания и ДатаОтправки (Дата и время).Шаг 2. Формирование отчетов и наполнение очереди
По кнопке пользователя или по регламентному расписанию запускается алгоритм, который выполняет компоновку отчета для каждого контрагента, сохраняет результат в табличный документ, переводит его в двоичные данные и записывает запись в очередь.
Посмотрим на пример кода формирования отчета и помещения вложения в очередь:
// Формируем табличный документ отчета программно
ТабДок = Новый ТабличныйДокумент;
// ... заполнение ТабДок через ПроцессорКомпоновкиДанных с отбором по Контрагенту ...
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("pdf");
ТабДок.Записать(ИмяВременногоФайла, ТипФайлаТабличногоДокумента.PDF);
ДвоичныеДанныеВложения = Новый ДвоичныеДанные(ИмяВременногоФайла);
УдалитьФайлы(ИмяВременногоФайла);
// Запись в регистр очереди
МенеджерЗаписи = РегистрыСведений.ОчередьИсходящейПочты.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Идентификатор = Новый УникальныйИдентификатор();
МенеджерЗаписи.Период = ТекущаяДатаСеанса();
МенеджерЗаписи.Контрагент = ТекущийКонтрагент;
МенеджерЗаписи.АдресПолучателя = АдресПочты;
МенеджерЗаписи.Тема = "Отчет за период";
МенеджерЗаписи.ФайлВложения = Новый ХранилищеЗначения(ДвоичныеДанныеВложения, Новый СжатиеДанных(9));
МенеджерЗаписи.Статус = Перечисления.СтатусыОтправкиПочты.ВТранспорте;
МенеджерЗаписи.Записать();
Шаг 3. Регламентная отправка пачками (пакетирование)
Чтобы не перегружать почтовый сервер и не попадать под блокировки спам-фильтров, создадим отдельное регламентное задание, которое периодически выбирает ограниченный пакет записей (например, по 50 писем) с помощью конструкции ВЫБРАТЬ ПЕРВЫЕ 50 и выполняет отправку.
При пакетной отправке критически важно держать SMTP-соединение открытым на протяжении всей пачки, а не вызывать методы подключения и отключения для каждого отдельного письма.
Рассмотрим пример процедуры фоновой отправки пачки писем:
Процедура ОтправитьОчередьПочты() Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 50
| Очередь.Идентификатор КАК Идентификатор,
| Очередь.АдресПолучателя КАК АдресПолучателя,
| Очередь.Тема КАК Тема,
| Очередь.ФайлВложения КАК ФайлВложения
|ИЗ
| РегистрСведений.ОчередьИсходящейПочты КАК Очередь
|ГДЕ
| Очередь.Статус = ЗНАЧЕНИЕ(Перечисление.СтатусыОтправкиПочты.ВТранспорте)
| И Очередь.КоличествоПопыток < 3
|УПОРЯДОЧИТЬ ПО
| Очередь.Период ВОЗР";
Результат = Запрос.Выполнить();
Если Результат.Пустой() Тогда
Возврат;
КонецЕсли;
Выборка = Результат.Выбрать();
Профиль = ПолучитьПочтовыйПрофиль(); // Функция получения ИнтернетПочтовыйПрофиль
Почта = Новый ИнтернетПочта;
Попытка
Почта.Подключиться(Профиль);
Исключение
ЗаписьЖурналаРегистрации("ОтправкаПочты", УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());
Возврат;
КонецПопытки;
Пока Выборка.Следующий() Цикл
Письмо = Новый ИнтернетПочтовоеСообщение;
Письмо.Получатели.Добавить(Выборка.АдресПолучателя);
Письмо.Тема = Выборка.Тема;
Письмо.Тексты.Добавить("Добрый день! Во вложении отчет за месяц.", ТипТекстаПочтовогоСообщения.ПростойТекст);
ДвоичныеДанныеФайла = Выборка.ФайлВложения.Получить();
Если ДвоичныеДанныеФайла <> Неопределено Тогда
ИмяВременного = ПолучитьИмяВременногоФайла("pdf");
ДвоичныеДанныеФайла.Записать(ИмяВременного);
Письмо.Вложения.Добавить(ИмяВременного, "Отчет.pdf");
КонецЕсли;
Попытка
Почта.Послать(Письмо);
ФиксироватьУспехОтправки(Выборка.Идентификатор);
Исключение
ФиксироватьОшибкуОтправки(Выборка.Идентификатор, ОписаниеОшибки());
КонецПопытки;
Если ЗначениеЗаполнено(ИмяВременного) Тогда
УдалитьФайлы(ИмяВременного);
КонецЕсли;
КонецЦикла;
Почта.Отключиться();
КонецПроцедуры
При работе с базами на тысячи контрагентов необходимо учитывать внешние технические ограничения:
ФоновыеЗадания.Выполнить(), разделив массив контрагентов на несколько параллельных потоков.Приостановить(КоличествоСекунд).