Интеграция почтовых систем с платформой 1С — классическая задача при автоматизации бизнес-процессов. Использование протокола EWS (Exchange Web Services) является современным и рекомендуемым способом взаимодействия с серверами Microsoft Exchange. В отличие от устаревших протоколов POP3 или IMAP, EWS предоставляет полный доступ к структуре папок, календарю, задачам и детальным свойствам писем.
Однако при реализации такого обмена разработчики часто сталкиваются с неочевидными техническими сложностями: перенаправлениями шлюзов безопасности (ошибка 302), отсутствием текста письма в результатах выборки и необходимостью связывать входящие ответы с ранее отправленными сообщениями. В этой статье мы подробно разберем все этапы настройки интеграции, проанализируем возникающие проблемы и разработаем готовое решение для вашей конфигурации 1С.
При попытке отправить прямой POST-запрос к веб-службе EWS с использованием стандартного объекта 1С HTTPСоединение, сервер может вернуть ответ со статусом 302 Found (или Moved Temporarily) и перенаправить запрос на адрес вида /my.policy. Выясним причину такого поведения.
Наличие в адресе перенаправления конструкции /my.policy однозначно указывает на то, что перед сервером Exchange установлен шлюз безопасности, например, F5 BIG-IP APM (Access Policy Manager) или аналогичные системы публикации (Microsoft TMG/UAG). Работает это следующим образом:
/EWS/Exchange.asmx.HTTPСоединение не умеет «под капотом» обрабатывать такие редиректы, сохранять сессионные куки (Session Cookies) и передавать их обратно. В результате процесс прерывается, и программа получает пустой ответ со статусом 302.Для решения этой проблемы мы можем использовать два принципиально разных подхода: программный обход через COM-объект Windows или корректную настройку параметров подключения в 1С.
Если сервер 1С работает под управлением операционной системы Windows, самым быстрым и надежным способом обхода шлюзов авторизации является использование системного COM-объекта MSXML2.XMLHTTP (или его более современной версии MSXML2.ServerXMLHTTP).
Этот объект работает поверх системных библиотек Windows (WinINet/WinHTTP), благодаря чему он автоматически умеет:
Рассмотрим пример кода, который успешно решает проблему авторизации и возвращает статус 200 OK:
// Создаем COM-объект для работы с HTTP-запросами
oXMLHTTP = Новый COMОбъект("MSXML2.XMLHTTP");
// Задаем адрес подключения к веб-сервису Exchange
АдресСервера = "https://mobile.testserver.com/EWS/Exchange.asmx";
Логин = "s1-test@testserver.com";
Пароль = "YourSecurePassword";
// Инициализируем POST-запрос. Третий параметр "Ложь" указывает на синхронный режим работы
oXMLHTTP.open("POST", АдресСервера, Ложь, Логин, Пароль);
// Устанавливаем обязательные заголовки для SOAP-запроса
oXMLHTTP.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
// Формируем тело SOAP-запроса для поиска писем (FindItem)
ТелоЗапроса = ПолучитьШаблонЗапросаFindItem();
// Отправляем запрос на сервер
oXMLHTTP.send(ТелоЗапроса);
// Проверяем результат выполнения
Если oXMLHTTP.Status = 200 Тогда
ВыборкаXML = oXMLHTTP.responseXML;
Сообщить("Соединение успешно установлено! Код 200.");
Иначе
Сообщить("Ошибка подключения: " + oXMLHTTP.Status + " - " + oXMLHTTP.statusText);
КонецЕсли;
Этот вариант отлично подходит для быстрой реализации обмена в Windows-окружении, но если ваше решение должно быть кроссплатформенным (например, сервер 1С работает на Linux), нам потребуется использовать исключительно штатные механизмы платформы.
Чтобы выполнить подключение с использованием встроенного объекта 1С HTTPСоединение и избежать ошибок авторизации на внешних шлюзах, проанализируем параметры защищенного соединения. Нам потребуется явно инициализировать сертификаты операционной системы.
Разберем по шагам правильный код инициализации подключения:
// 1. Инициализируем защищенное соединение SSL с использованием системных сертификатов
// Это необходимо, чтобы 1С доверяла SSL-сертификату сервера Exchange
ЗащищенноеСоединение = Новый ЗащищенноеСоединениеOpenSSL(
Неопределено,
Новый СертификатыУдостоверяющихЦентровWindows()
);
// 2. Указываем точное имя сервера без протокола HTTPS и путей
СерверEWS = "mobile.testserver.com";
Порт = 443;
Логин = "s1-test@testserver.com";
Пароль = "YourSecurePassword";
// 3. Создаем HTTP-соединение. Обратите внимание на использование тайм-аутов
Соединение = Новый HTTPСоединение(
СерверEWS,
Порт,
Логин,
Пароль,
Неопределено, // Прокси
30, // Тайм-аут соединения в секундах
ЗащищенноеСоединение
);
// 4. Формируем запрос к ресурсу веб-сервиса
РесурсНаСервере = "/EWS/Exchange.asmx";
Заголовки = Новый Соответствие();
Заголовки.Вставить("Content-Type", "text/xml; charset=utf-8");
ЗапросHTTP = Новый HTTPЗапрос(РесурсНаСервере, Заголовки);
// Устанавливаем тело XML (например, структуру FindItem)
ЗапросHTTP.УстановитьТелоИзСтроки(ТелоЗапроса, КодировкаТекста.UTF8);
// 5. Отправляем запрос и обрабатываем результат
Попытка
Результат = Соединение.ОтправитьДляОбработки(ЗапросHTTP);
Если Результат.КодСостояния = 200 Тогда
Сообщить("Успешный ответ от сервера Exchange через HTTPСоединение!");
ИначеЕсли Результат.КодСостояния = 302 Тогда
// Обработка ручного редиректа, если шлюз все же перенаправил запрос
НовыйРесурс = Результат.Заголовки.Получить("Location");
Сообщить("Сервер вернул редирект на: " + НовыйРесурс);
КонецЕсли;
Исключение
Сообщить("Ошибка при отправке запроса: " + ОписаниеОшибки());
КонецПопытки;
Важное примечание для системных администраторов: Если вы настраиваете интеграцию внутри корпоративной сети, наиболее правильным путем будет обращение к администраторам почтового сервера. Необходимо попросить их добавить исключение для виртуального каталога /EWS/* на шлюзе безопасности (например, F5 BIG-IP), отключив для него перенаправление на веб-интерфейс авторизации /my.policy и оставив стандартную сквозную Windows-авторизацию (NTLM) или Basic Auth.
Когда соединение успешно установлено, разработчики переходят к чтению почтового ящика. Для этого традиционно используется SOAP-операция FindItem. Однако при разборе XML-ответа выясняется, что в структуре полученных писем полностью отсутствует тег Body (тело сообщения). Попытки принудительно добавить свойство в запрос через схему item:Body приводят к ошибке схемы валидации: The 'FieldURI' attribute is invalid.
Проанализируем архитектуру EWS. Спецификация Microsoft Exchange Web Services накладывает жесткое ограничение на использование операции FindItem. Эта операция предназначена исключительно для быстрого поиска и получения списка метаданных писем (заголовок, отправитель, дата, флаг прочтения, ID). Возврат тела письма (которое может быть огромного объема) в рамках списочного метода заблокирован на уровне платформы Exchange для экономии ресурсов сервера и сети.
Чтобы получить текст и HTML-содержимое конкретного письма, необходимо использовать двухэтапный алгоритм:
FindItem, чтобы получить список уникальных идентификаторов писем (ItemId и его ChangeKey).GetItem, которая вернет полную информацию по письму, включая его тело.Посмотрим на структуру XML-запроса для операции GetItem, запрашивающей тело письма:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages"
xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">
<soap:Body>
<m:GetItem>
<m:ItemShape>
<t:BaseShape>Default</t:BaseShape>
<!-- Явно просим вернуть тело письма в формате HTML -->
<t:BodyType>HTML</t:BodyType>
<t:AdditionalProperties>
<t:FieldURI FieldURI="item:Body"/>
</t:AdditionalProperties>
</m:ItemShape>
<m:ItemIds>
<!-- Подставляем полученные из FindItem значения Id и ChangeKey -->
<t:ItemId Id="AAMkAGMzY2..." ChangeKey="CQAAABY..." />
</m:ItemIds>
</m:GetItem>
</soap:Body>
</soap:Envelope>
Используя этот метод, вы гарантированно получите тег <t:Body BodyType="HTML">...</t:Body> в ответе сервера и сможете загрузить текст письма в 1С.
При ведении деловой переписки критически важно понимать контекст: является ли пришедшее письмо новым обращением или это ответ клиента на наше исходящее письмо из 1С. Попытка связать письма по теме (Subject) ненадежна, так как пользователи могут менять тему или она может содержать общие фразы.
Для однозначного связывания писем в цепочки (треды) нам необходимо использовать уникальные глобальные интернет-идентификаторы писем, которые формируются почтовыми серверами согласно стандарту RFC 2822. Обычный ItemId в Exchange для этого не подходит, так как он динамический и меняется при перемещении письма между папками (например, из «Входящих» в «Архив»).
При вызове метода GetItem мы должны запросить дополнительные свойства, расширив секцию <t:AdditionalProperties> следующими полями:
item:InternetMessageId — уникальный глобальный идентификатор текущего письма (выглядит как <ID@домен>). Он генерируется сервером-отправителем и никогда не меняется.item:InReplyTo — содержит InternetMessageId того родительского письма, на которое был отправлен данный ответ.item:References — содержит упорядоченный список идентификаторов всех писем из этой цепочки переписки.item:ConversationId — сквозной идентификатор всей беседы в Exchange. Позволяет мгновенно получить всю ветку переписки одной выборкой.Разберем пример XML-шаблона запроса GetItem для получения всех связующих полей:
<t:AdditionalProperties>
<t:FieldURI FieldURI="item:InternetMessageId" />
<t:FieldURI FieldURI="item:InReplyTo" />
<t:FieldURI FieldURI="item:References" />
<t:FieldURI FieldURI="item:ConversationId" />
<t:FieldURI FieldURI="item:Body" />
</t:AdditionalProperties>
Для хранения связей рекомендуется создать в 1С регистр сведений или добавить реквизиты в документ (например, в «ЭлектронноеПисьмоВходящее»). Рассмотрим логику работы обработчика загрузки почты:
// Предположим, мы получили свойства загружаемого письма из XML-ответа EWS
ТекущийInternetID = "<DEB70E4F6DD8234E832A7AFE401213A5015AAE8E4B@satsbgexch01.local>";
РодительскийInternetID = "<5f4d8a11-8c22-4211-9a77-3e112cb92ad4@my1cserver.ru>";
// Шаг 1. Ищем, есть ли в базе 1С письмо с таким же InternetMessageId (защита от дублей)
НайденноеПисьмо = Документы.ЭлектронноеПисьмоВходящее.НайтиПоРеквизиту("ИдентификаторСообщения", ТекущийInternetID);
Если ЗначениеЗаполнено(НайденноеПисьмо) Тогда
// Письмо уже загружено, пропускаем его
Возврат;
КонецЕсли;
// Шаг 2. Создаем новый документ письма в 1С
НовоеПисьмо = Документы.ЭлектронноеПисьмоВходящее.СоздатьДокумент();
НовоеПисьмо.Дата = ТекущаяДата();
НовоеПисьмо.ИдентификаторСообщения = ТекущийInternetID;
НовоеПисьмо.Тема = "RE: Важная тема";
// Шаг 3. Реализуем связывание писем
Если ЗначениеЗаполнено(РодительскийInternetID) Тогда
// Ищем в базе исходное (наше исходящее) письмо по его ID
ПисьмоОснование = Документы.ЭлектронноеПисьмоИсходящее.НайтиПоРеквизиту("ИдентификаторСообщения", РодительскийInternetID);
Если Не ЗначениеЗаполнено(ПисьмоОснование) Тогда
// Если исходящего нет, возможно родительское письмо тоже было входящим
ПисьмоОснование = Документы.ЭлектронноеПисьмоВходящее.НайтиПоРеквизиту("ИдентификаторСообщения", РодительскийInternetID);
КонецЕсли;
Если ЗначениеЗаполнено(ПисьмоОснование) Тогда
// Устанавливаем связь. В типовых конфигурациях 1С обычно используется реквизит "ВзаимодействиеОснование"
НовоеПисьмо.ВзаимодействиеОснование = ПисьмоОснование;
Сообщить("Письмо успешно привязано к цепочке переписки!");
КонецЕсли;
КонецЕсли;
НовоеПисьмо.Записать();
Применяя этот подход, мы гарантируем абсолютную целостность переписки. Даже если клиент удалит из темы письма префиксы «Re:» или полностью изменит название темы, платформа 1С безошибочно определит родительское сообщение благодаря неизменным скрытым заголовкам почтового сервера In-Reply-To.