При автоматической отправке писем из 1С разработчики часто сталкиваются с крайне неприятной проблемой: файлы вложений (будь то архивы ZIP, документы Excel или PDF-файлы) доходят до адресата поврежденными. Причем это происходит бессистемно — например, из четырех отправленных писем два открываются без проблем, а в остальных двух вложение оказывается «битым». С такой же проблемой столкнулся и автор темы на форуме.
В этой статье мы подробно проанализируем причины этой скрытой ошибки, разберем физику процессов, происходящих при формировании писем в 1С, и предложим комплексное и надежное решение, которое навсегда избавит вас от поврежденных файлов во вложениях.
Рассмотрим стандартный сценарий, который часто используют разработчики. В коде сначала формируется табличный документ, записывается на диск, затем упаковывается в ZIP-архив, и полученный архив тут же передается в качестве вложения в объект ИнтернетПочтовоеСообщение. Сразу после этого вызывается метод отправки почты.
В этот момент на физическом уровне происходит следующее: программа 1С дает команду операционной системе записать файл на диск. Как только файл создается, в работу мгновенно включается установленный на сервере антивирус. Он перехватывает новый файл для сканирования и накладывает на него монопольную блокировку. Если в этот же микросекундный интервал времени платформа 1С пытается прочитать данный файл для прикрепления к письму, происходит конфликт совместного доступа. Файл либо считывается не полностью (из-за чего вложение «бьется»), либо 1С вообще не может получить к нему доступ.
Использование фиксированного имени файла усугубляет ситуацию: разные фоновые задания или потоки могут одновременно пытаться перезаписать один и тот же файл в одном каталоге.
Обратите внимание на фрагмент кода автора темы:
АрхивФайлов = Новый ЗаписьZipФайла(Каталог + "\" + ИмяФайлаZIP, , , МетодСжатияZIP.Сжатие, УровеньСжатияZIP.Оптимальный);
АрхивФайлов.Добавить(Каталог + "\" + ИмяФайла);
АрхивФайлов.Записать;
Здесь вызов метода Записать написан без круглых скобок. Во встроенном языке 1С допускается опускать скобки для процедур, не имеющих параметров. Однако в зависимости от версии платформы обращение к методу объекта как к свойству (без скобок) может обрабатываться некорректно. В результате буфер архива не сбрасывается на диск вовремя (не происходит принудительный «flush»), и на момент отправки файл на диске остается незавершенным или пустым.
Правило хорошего тона: всегда вызывайте методы объектов с явным указанием круглых скобок:
АрхивФайлов.Записать();
Многие программисты для экономии времени создают письма, содержащие только тему и вложение, оставляя текстовое содержание пустым. Это серьезная ошибка при работе с объектом ИнтернетПочта.
Почтовые серверы (особенно крупные, такие как Yandex, Mail.ru, Exchange) очень строго следят за соблюдением стандартов MIME. Когда формируется почтовое сообщение без текстовой части, почтовые клиенты получателей (например, MS Outlook) или сами спам-фильтры серверов могут некорректно интерпретировать границы («boundaries») частей MIME-сообщения. В результате бинарные данные вложения сливаются с заголовками письма, декодируются неправильно, и файл приходит получателю поврежденным.
Для решения этой проблемы достаточно добавить хотя бы минимальный текст (или даже один пробел) в коллекцию Тексты сообщения:
Письмо.Тексты.Добавить("Во вложении находится сформированный отчет.");
В платформе 1С:Предприятие 8.3.13 для работы с ZIP-архивами использовалась сторонняя библиотека Info-ZIP. В этой версии были зафиксированы официальные ошибки, приводившие к порче архивов при наличии кириллицы в именах упаковываемых файлов или при высокой интенсивности дисковых операций. Если вы используете эту ветку платформы, настоятельно рекомендуется обновить ее до актуальных стабильных релизов (8.3.14 и выше).
Давайте разберем по шагам, как построить максимально надежный и отказоустойчивый процесс формирования и отправки вложений, который исключит любые блокировки и искажения данных.
Шаг 1. Использование уникальных временных файлов. Чтобы исключить пересечение потоков, для каждого вложения мы будем генерировать уникальное имя с помощью метода ПолучитьИмяВременногоФайла().
Шаг 2. Чтение файла в Двоичные Данные. Вместо того чтобы передавать в метод Письмо.Вложения.Добавить() строковый путь к файлу (что заставляет платформу считывать его непосредственно в момент отправки), мы заранее прочитаем файл в оперативную память в виде объекта ДвоичныеДанные. Если файл заблокирован антивирусом, мы поймаем эту ошибку на этапе чтения, а не в процессе отправки.
Шаг 3. Обязательное заполнение тела письма. Добавим содержательный текст в письмо, чтобы соблюсти стандарты MIME-формата.
Посмотрим на пример реализации этого надежного механизма:
// 1. Определение уникальных путей для временных файлов
ИмяФайлаXLSX = ПолучитьИмяВременногоФайла("xlsx");
ИмяФайлаZIP = ПолучитьИмяВременногоФайла("zip");
// 2. Запись табличного документа во временный файл
ТабДокумент.Записать(ИмяФайлаXLSX, ТипФайлаТабличногоДокумента.XLSX);
// 3. Создание архива с явным вызовом методов
АрхивФайлов = Новый ЗаписьZipФайла(ИмяФайлаZIP, , , МетодСжатияZIP.Сжатие, УровеньСжатияZIP.Оптимальный);
АрхивФайлов.Добавить(ИмяФайлаXLSX);
АрхивФайлов.Записать(); // Обязательно со скобками!
// Освобождаем память от объекта архива, чтобы закрыть файловый дескриптор
АрхивФайлов = Неопределено;
// 4. Безопасное чтение файла в память (с обработкой возможных задержек ОС)
ДвоичныеДанныеВложения = Неопределено;
Для НомерПопытки = 1 По 5 Цикл
Попытка
ДвоичныеДанныеВложения = Новый ДвоичныеДанные(ИмяФайлаZIP);
Прервать; // Успешно прочитали, выходим из цикла
Исключение
// Если файл заблокирован антивирусом, ждем 1 секунду и повторяем попытку
Приостановить(1);
КонецПопытки;
КонецЦикла;
Если ДвоичныеДанныеВложения = Неопределено Тогда
ВызватьИсключение "Не удалось прочитать файл архива для отправки почты. Файл заблокирован.";
КонецЕсли;
// 5. Формирование почтового сообщения
Письмо = Новый ИнтернетПочтовоеСообщение;
Письмо.Тема = "Контроль за отгруженными ЗН";
Письмо.ИмяОтправителя = "Иванов Т. А.";
Письмо.Отправитель = "test@ya.ru";
Письмо.Получатели.Добавить("test@ya.ru");
// Обязательно добавляем текст письма для корректного MIME-формата
Письмо.Тексты.Добавить("Добрый день! Во вложении к письму находится отчет.");
// Добавляем вложение как ДвоичныеДанные, задавая желаемое имя файла получателю
Письмо.Вложения.Добавить(ДвоичныеДанныеВложения, "Отгруженные_ЗН.zip");
// 6. Подключение и отправка
ПараметрыПочты = Новый ИнтернетПочтовыйПрофиль;
ПараметрыПочты.АдресСервераSMTP = "test@ya.ru";
ПараметрыПочты.АутентификацияSMTP = СпособSMTPАутентификации.ПоУмолчанию;
ПараметрыПочты.ПользовательSMTP = "test@ya.ru";
ПараметрыПочты.ПарольSMTP = "*****";
Подключение = Новый ИнтернетПочта;
Попытка
Подключение.Подключиться(ПараметрыПочты);
Подключение.Послать(Письмо);
Подключение.Отключиться();
Сообщить("Письмо отправлено успешно.");
Исключение
Сообщить("Письмо не отправлено. Ошибка: " + ОписаниеОшибки());
КонецПопытки;
// 7. Очистка временных файлов на диске
Попытка
УдалитьФайлы(ИмяФайлаXLSX);
УдалитьФайлы(ИмяФайлаZIP);
Исключение
// Игнорируем ошибки удаления временных файлов, ОС очистит их позже
КонецПопытки;
Для того чтобы файлы вложений при отправке почты из 1С гарантированно доходили до получателя в целости, придерживайтесь трех золотых правил:
Письмо.Тексты.ДвоичныеДанные.ПолучитьИмяВременногоФайла(), чтобы избежать конфликтов блокировок файлов разными сессиями пользователей или фоновыми заданиями.