Как правильно ускорить массовую отправку писем через объект ИнтернетПочта в 1С?

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

При массовой рассылке писем (например, счетов, актов сверки или расчетных листков) стандартная схема отправки нередко работает непозволительно медленно. Метод Подключиться() объекта ИнтернетПочта выполняет установку сетевого соединения, TLS-рукопожатие и авторизацию на SMTP-сервере, что может занимать от 1 до 3 секунд на каждое отдельное письмо. При сотнях контрагентов процедура растягивается на десятки минут.

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

Анализ проблемы: почему исчезают письма и молчит исключение

Рассмотрим типовую оптимизированную схему, которую часто пытаются реализовать в обработках:


Профиль = Новый ИнтернетПочтовыйПрофиль;
// Заполнение настроек профиля (АдресСервераSMTP, Порт, ПользовательSMTP, ПарольSMTP и т.д.)

Почта = Новый ИнтернетПочта;
Почта.Подключиться(Профиль);

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

Почта.Отключиться();

На практике оказывается, что часть писем не доходит до адресатов, хотя в блок Исключение код ни разу не попадает. Выясним причину такого поведения.

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

  1. Ключ — проблемный адрес электронной почты;
  2. Значение — текст ошибки или диагностический ответ SMTP-сервера.

Если результат вызова метода Послать() не проверяется, 1С считает отправку успешной, скрывая ошибки отклонения сообщений почтовым сервером.

Ограничения почтовых серверов при едином соединении

Второй скрытый фактор заключается в политиках безопасности почтовых серверов (включая Microsoft Exchange, Postfix, Exim и публичные сервисы):

  1. Лимит сообщений на соединение (MaxMessagesPerConnection). Многие серверы разрешают передать не более 20–50 писем за одну непрерывную сессию, после чего принудительно обрывают сокет или перестают принимать команды DATA.
  2. Таймаут простоя (Inactivity Timeout). Если внутри цикла между вызовами метода Послать() программа формирует печатные формы, выполняет тяжелые запросы к базе или сохраняет большие файлы на диск, пауза между SMTP-командами может превысить таймаут ожидания сервера. В результате сервер закрывает соединение по неактивности.
  3. Защита от спама (Tarpitting и Rate Limiting). При слишком интенсивной отправке сервер искусственно задерживает ответы на команды или временно блокирует поток отправлений.

Решение 1: Пакетная отправка с контролем возвращаемого значения

Для стабильной работы разделим процесс на подготовку и саму отправку, а также разобьем всю пачку сообщений на порции (пакеты по 20–30 писем) с обязательной проверкой возвращаемого соответствия.

Посмотрим на пример надежной реализации:


// Шаг 1. Предварительное формирование массива сообщений
МассивСообщений = Новый Массив;
Для Каждого ДанныеПолучателя Из ТаблицаДанных Цикл
    Сообщение = Новый ИнтернетПочтовоеСообщение;
    Сообщение.Тема = ДанныеПолучателя.Тема;
    Сообщение.Получатели.Добавить(ДанныеПолучателя.Адрес);
    // Добавление текстов и вложений выполняется ДО открытия сессии SMTP
    МассивСообщений.Добавить(Сообщение);
КонецЦикла;

// Шаг 2. Пакетная отправка порциями
РазмерПакета = 25;
Индекс = 0;
ВсегоПисем = МассивСообщений.Количество();

Почта = Новый ИнтернетПочта;

Пока Индекс < ВсегоПисем Цикл
    
    Попытка
        Почта.Подключиться(Профиль);
    Исключение
        ЗаписьЖурналаРегистрации("ОтправкаПочты.Подключение", УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());
        Прервать;
    КонецПопытки;
    
    НомерВПакете = 0;
    Пока НомерВПакете < РазмерПакета И Индекс < ВсегоПисем Цикл
        ТекущееПисьмо = МассивСообщений[Индекс];
        
        Попытка
            ОшибкиОтправки = Почта.Послать(ТекущееПисьмо);
            
            Если ТипЗнч(ОшибкиОтправки) = Тип("Соответствие") И ОшибкиОтправки.Количество() > 0 Тогда
                Для Каждого ОшибкаАдреса Из ОшибкиОтправки Цикл
                    ЗаписьЖурналаРегистрации("ОтправкаПочты.ОтказСервера", 
                        УровеньЖурналаРегистрации.Предупреждение,,,
                        СтрШаблон("Адрес: %1, Ошибка: %2", ОшибкаАдреса.Ключ, ОшибкаАдреса.Значение));
                КонецЦикла;
            КонецЕсли;
        Исключение
            ЗаписьЖурналаРегистрации("ОтправкаПочты.СбойСессии", УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());
            Прервать; // Выходим из пакета для переподключения
        КонецПопытки;
        
        Индекс = Индекс + 1;
        НомерВПакете = НомерВПакете + 1;
    КонецЦикла;
    
    Если Почта.Подключена() Тогда
        Почта.Отключиться();
    КонецЕсли;
    
КонецЦикла;

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

Решение 2: Использование подсистем БСП

Если конфигурация построена на базе Библиотеки стандартных подсистем (БСП), не обязательно писать собственный механизм управления сессиями. Проанализируем возможности подсистемы РаботаСПочтовымиСообщениями.

Функция РаботаСПочтовымиСообщениями.ОтправитьПисьма() уже содержит внутри алгоритмы автоматической разбивки на порции, повторных попыток подключения при сетевых сбоях и корректного логирования ошибок отправки по каждому получателю в регистры сведений.

Архитектурные рекомендации по ускорению рассылки

Если требуется отправлять тысячи писем без замедления работы пользователей, воспользуемся следующими архитектурными практиками:

  1. Асинхронная очередь сообщений. Запись формируемых писем производится в специальный регистр или справочник очереди. Отправку осуществляет отдельное регламентное задание в фоне, не блокируя пользовательский интерфейс.
  2. Параллельная отправка через фоновые задания. Общий массив подготовленных писем делится на несколько частей, и для каждого потока запускается свое фоновое задание через ФоновыеЗадания.Выполнить(). Каждый поток создает свой экземпляр ИнтернетПочта и параллельно отправляет свой пакет.
  3. Локальный SMTP-ретранслятор (Relay). Развертывание локального почтового сервера (например, на том же сервере, где работает сервер 1С:Предприятия). Платформа 1С мгновенно отдает сформированные письма локальной службе по локальной сети без внешних сетевых задержек и шифрования сокетов. Дальнейшую доставку, повторы при отказах и управление очередями берет на себя почтовый сервис операционной системы.
← На главную