Содержание:
2. Пример удачного внедрения программы 1С
В процессе работы вам может понадобиться интеграция с почтовыми сервисами для вашей конфигурации. Это полезное решение с точки зрения бизнеса, которое позволяет анализировать не только активность сотрудников, но и тексты всех их корпоративных сообщений.
Однако для программиста такая на вид простая задача может оказаться огромной головной болью, ведь вся информация об устройстве электронных писем и самих технологиях либо разбросана кусками по всему интернету, либо подается не справедливо сложно.
В этой статье я постараюсь донести главную теорию об электронных письмах с точки зрения 1С разработчика, опишу проблемы, с которыми сталкивался, и возможные варианты решения.
1. Общая теория электронных сообщений в системе программы 1С
В рамках теории стоит упомянуть только то, что вам понадобится как разработчику.
Электронное письмо — это набор информации, состоящий из 3 основных частей:
1. Служебный заголовок.
2. Тело.
3. Вложения.
Для передачи этих трех компонентов придумали три основных протокола POP, IMAP, SMTP. Рассмотрим их подробнее:
• POP. В концепции почтового хранилища почта на сервере хранится временно, в ограниченном объёме (аналогично почтовому ящику для бумажной почты), а пользователь периодически обращается к ящику и «забирает» письма (то есть почтовый клиент скачивает копию письма к себе и удаляет оригинал из почтового ящика). На основании этой концепции действует протокол POP3.
• IMAP. Концепция почтового терминала подразумевает, что вся корреспонденция, связанная с почтовым ящиком (включая копии отправленных писем), хранится на сервере, а пользователь обращается к хранилищу (иногда его по традиции также называют «почтовым ящиком») для просмотра корреспонденции (как новой, так и архива) и написания новых писем (включая ответы на другие письма). На этом принципе действует протокол IMAP и большинство веб-интерфейсов бесплатных почтовых служб. Подобное хранение почтовой переписки требует значительно больших мощностей от почтовых серверов, в результате, во многих случаях происходит разделение между почтовыми серверами, пересылающими почту, и серверами хранения писем.
• SMTP. Простой протокол связи, применяемый с целью пересылки электронных писем с сервера отправителя на сервер получателя. Этот протокол не рассчитан на обработку входящих сообщений, его используют для отправки и последующей доставки писем адресату. Преимущественно с помощью SMTP отправляют массовые и транзакционные рассылки.
То есть, с точки зрения будущей интеграции, протоколы можно разделить на входящие (IMAP/POP) и исходящие (SMTP).
C помощью SMTP вы можете попросить почтовый сервер отправить сформированное вами почтовое сообщение, а IMAP/POP доставит вам сообщения с него.
Главная разница между IMAP и POP во взаимодействии с почтовым сервером, IMAP позволяет работать с письмами динамически, т.е делать отбор по значениям заголовков писем. В то время как POP позволит только скачать все письма, которые хранятся на сервере и никак иначе.
Во второй части разница между этими подходами станет более понятна и очевидна.
2. Пример удачного внедрения программы 1С
Представим себе задачу, необходимо сохранять всю почту 40 сотрудников компании, с учетом переписок.
В ходе изучения теории электронных писем, можно прийти к выводу что ключевым звеном является Служебный заголовок.
По сути Служебный заголовок — это общее название подзаголовков о том, кто, кому, когда, и через кого отправляет сообщение.
К основным заголовкам можно отнести:
Return-Path: — адрес возврата в случае неудачи, когда невозможно доставить письмо по адресу назначения.
Received: — данные о прохождении письма через каждый конкретный почтовый сервер. При прохождении через несколько почтовых серверов (обычная ситуация), новые заголовки дописываются над предыдущими, в конечном итоге журнал перемещения будет записан в обратном порядке (от ближайшего к получателю узла к самому дальнему).
MIME-Version: — версия MIME, с которым это сообщение создано. Зачастую этот заголовок создаётся раньше всех остальных, поэтому он обычно самый первый (то есть последний в списке).
From: — имя и адрес отправителя (именно в этом заголовке появляется текстовое поле с именем отправителя).
Sender: — отправитель письма. Добавлено для возможности указать, что письмо от чьего-то имени (from) отправлено другой персоной (например, секретарём от имени начальника).
To: — имя и адрес получателя. Может содержаться несколько раз (если письмо адресовано нескольким получателям).
Cc: —содержит имена и адреса вторичных получателей письма, к которым направляется копия.
Bcc: —содержит имена и адреса получателей письма, чьи адреса не следует показывать другим получателям. Участвует в формировании поля SMTP RCPT TO, как поля «To» и «Cc», но отсутствует в отправляемом сообщении.
Reply-To: — имя и адрес, куда следует адресовать ответы на это письмо. Если, например, письмо рассылается роботом, то в качестве Reply-To будет указан адрес почтового ящика, готового принять ответ на письмо.
Message-ID: — уникальный идентификатор сообщения. Состоит из адреса узла-отправителя и номера (уникального в пределах узла). Алгоритм генерации уникального номера зависит от сервера/клиента.
Выглядит примерно так: AAB77AA2175ADD4BACECE2A49988705C0C93BB7B4A@example.com.
In-Reply-To: — указывает на Message-ID, для которого это письмо является ответом (с помощью этого почтовые клиенты могут легко выстраивать цепочку переписки — каждый новый ответ содержит Message-ID для предыдущего сообщения).
Subject: — тема письма.
Date: — дата отправки письма.
Content-Type: — тип содержимого письма (HTML, RTF, Plain text) и кодировка, в которой создано письмо.
Return-Receipt-To: — e-mail, куда почтовый сервер получателя должен отправить уведомление о доставке.
Disposition-Notification-To: — e-mail, куда почтовый клиент получателя должен отправить уведомление о доставке, если это разрешит пользователь (посредством настроек и т. п.).
References: - последовательность Message-ID в переписке. Не все почтовые сервисы ведут этот заголовок.
Помимо стандартных, почтовые клиенты, серверы и роботы обработки почты могут добавлять свои собственные заголовки, начинающиеся с «X-». Как правило, их лучше игнорировать.
Интуитивно люди делят электронные письма на исходящие и входящие, этому нас научили почтовые сервисы, однако такой подход оптимален при большом потоке почты, так как сможет ускорить получение конкретных типов писем из базы, в случае малой нагрузки, более оптимальным решением будет объединить письма в один документ.
Объединение писем в один документ позволит не хранить дубликаты. Рассмотрим на примере.
Существует три почты с которых происходит скачивание писем, с Почты 1 на Почту 2 и 3 отправляется сообщение. В результате полностью идентичное письмо будет скачено 3 раза, то есть с каждой почты по копии. На этапе записи мы сможем остановить запись второго и третьего письма, так как одно такое уже есть.
Однако при разделенном варианте хранения нам придется оставить в базе 2 копии письма, как исходящее для почты 1 и как входящее для почты 2 и 3.
«Свертывание» писем в документе производить довольно легко, у каждого электронного письма есть идентификатор Message-ID, который уникален для всех сообщений в мире. При нахождении двух писем с одинаковым Message-ID одно из них можно удалить, либо свернуть с другим. Оба эти подхода рассмотрим дальше в статье.
После решения о хранении писем, появляется вопрос о построении переписке, как ветки, так и древа в целом.
Когда почтовый сервис отправляет сообщение в ответ, в заголовке письма In-Reply-To он укажет идентификатор письма, на которое происходит ответ. Выглядеть это будет примерно так In-Reply-To: <1694083965.511343144@f147.i.mail.ru>. К сожалению, ИнтернетПочтовоеСообщение не получает эти данные, поэтому придется считывать заголовок в поисках идентификатора.
Бывают ситуации, когда вы точно знаете, что сообщение было отправлено в ответ на другое, а заголовка In-Reply-To нет. Для такого случая последним шансом построения переписки станет заголовок References. После этого заголовка идут все Message-ID переписки, но не советую использовать этот заголовок как основной для переписки, многие сервисы либо не ставят его, либо не дописывают все Message-ID. Искать
References имеет смысл только при отсутствии In-Reply-To.
После решения всех вопросов в теории, можно приступать к практике.
В первую очередь создадим процедуру загрузки писем с почтового сервиса. Для этого в 1С встроен объект ИнтеренетПочта.
В примере происходит объявление ИнтернетПочта, и соединение с помощью нее и ИнтернетПочтовыйПрофиль к самой. При успешном подключении будет доступно выполнять команды взаимодействия.
ИнтеренетПочтовыйПрофиль создается на основании известных данных подключения. Уточнять данные необходимо у почтовых сервисов.
Пример Mail.ru:
Mail.ru
После успешного подключения почты 1С, командой Выбрать() можно получить все письма с текущего почтового ящика.
Важно обратить внимание на параметры команды.
Выбрать(<УдалятьСообщения>,<МассивЗаголовковСообщенийИлиИдентификаторов>,<ОтмечатьКакПрочтенные>)
<УдалятьСообщения> - Истина - удалять выбранные сообщения с сервера.
<МассивЗаголовковСообщенийИлиИдентификаторов> - Массив, содержащий либо заголовки сообщений, либо серверные идентификаторы сообщений 1С, которые необходимо получить.
<ОтмечатьКакПрочтенные> - Отмечать письма на сервере как прочтенные.
Следует быть очень аккуратным с параметром УдалятьСообщения. Не каждый почтовый сервис даст возможность необходимости их восстановить.
МассивЗаголовковСообщенийИлиИдентификаторов позволяет нам не получать лишние сообщения, в массив необходимо вложить IMAP идентификаторы писем, которые вас интересуют. Для получения этих идентификаторов существует метод ПолучитьИдентфикаторы().
В метод ПолучитьИдентфикаторы() первым параметром необходимо передать все IMAP идентификаторы которые нужно исключить при получении. Чаще всего это те письма, которые были скачаны ранее. IMAP идентификатор письма можно найти в полученном ИнтернетПочтовомСообщение.
Вторым параметром передаются настройки отбора писем. Настройки представляют собой структуру, в которой ключ - параметр отбора, а значение – условие отбора. При задании нескольких условий отбора их связь происходит через логическое И.
Список возможных условий отбора 1С:
- ОтправленОтвет - Булево. Отобрать сообщения, у которых установлен флаг – Answered(Ответ);
- Недавние - Булево. Отобрать сообщения, пришедшие в рамках текущей IMAP-сессии.
- СлепыеКопии - Строка. Отобрать сообщения, которые имеют “строка” в поле Bcc;
- Копии - Строка. Отобрать сообщения, которые имеют “строка” в поле Cc;
- Получатели - Строка. Отобрать сообщения, которые имеют “строка” в поле To;
- ДатаОтправления - Дата. Отобрать сообщения, у которых значение поле Date: равно “Дата”;
- Отправитель - Строка. Отобрать все сообщения у которых встречается “строка”в поле From;
- ДоДатыОтправления - Дата. Отобрать сообщения, у которых значение поле Date: перед “дата”;
- ПослеДатыОтправления - Дата. Отобрать сообщения, у которых значение поля Date: после значения “Дата”;
- Тема - Строка. Отобрать сообщения, в заголовке которых встречается заданная строка;
- Текст - Строка. Отобрать сообщения, в любых текстовых полях которого встречается заданная строка;
- ТелоСообщения - Строка. Отобрать сообщения, в теле которых встречается строка – “строка”;
- Удаленные - Булево. Отобрать сообщения, которые должны быть удалены или не должны быть удалены;
- УстановленФлаг - Булево. Отобрать сообщения, которые помечены флагом или не помечены флагом;
- Прочитанные - Булево. Отобрать сообщения, которые были прочитаны или не прочитаны;
Таким образом код для получения всех писем, исключая ранее загруженные, и с упоминаем подстроки «Семен» в теме, будет выглядеть так:
Для получения исходящих писем необходимо проделать все те же действия, но перед получением писем указать исходящий ящик.
Метод ПолучтьПочтовыеЯщики() возвращает массив названий всех ящиков. Исходящий ящик чаще всего называется SENT или Отправленные.
После перебора имен настраиваем соединение на нужный ящик, и повторяем получение.
При этом IMAP идентификатор исходящих писем будет отличаться от входящих, это позволит хранить их в одном месте, а получать только необходимые.
После получения массива писем преобразуем его в таблицу значений. Это даст возможность более удобной обработки на дубли, особенно если в одну таблицу значений поместить письма с нескольких почтовых ящиков.
В итоговой таблице значений можно найти все дублирующиеся письма, то есть те письма, у которых одинаковый Message-ID. Заголовок, тело и вложения таких писем полностью идентичны, за одним исключением – слепые копии.
На этом моменте и станет понятна разница между удалением и свертываем дублей При отправке письма нескольким людям через Слепые копии, ни один из получателей не будет видеть других, а значит при загрузке письма в базу, получатели будут отсутствовать.
Если удалять все дубли, то в базу попадет письмо без получателей. В ситуации, когда Слепые письма нужно хранить вместе со всеми получателями, выход достаточно простой. Каждое загружаемое письмо помечаем почтовым адресом, с которого оно было получено. После чего перемещаем всех получателей в одно из писем, а остальные удаляем.
Когда все дубли будут удалены, наступает момент записи. На этом этапе стоит уделить внимание правильной записи текста, вложений и «родителя» письма, остальные данные интуитивно понятны.
Текст письма приходит в двух основных видах: 1. Обычный текст 2. Гипертекст
Виды текста письма
Все виды текста рекомендую поместить в хранилище значений вместе с заголовком письма, в виде структуры или соответствия. Хранить их в строковых полях неограниченной длины бесполезно, а часто и вредно. Функционал хранилища и такого поля почти не будет отличаться, однако хранилище будет сжимать данные, а экономия памяти всегда приятна.
Вывод текста на форму зависит от вашей фантазии, можно выводить все варианты текстов, можно выводить красивый гипертекст, а можно и вовсе в качестве экономии изначально сохранять только один вид.
Вложения писем состоят из двух условных частей: 1. Вложения тела письма 2. Обычные вложения.
Вложения писем
Обычные вложения — это документы, картинки, музыка и другие виды файлов, которые прикрепил к письму отправитель. Каждое вложение состоит из имени, двоичных данных, идентификатора, кодировки и типа содержимого.
Вложения тела письма — это изображения, которые встроенные прямо в текст. Отличить их можно по идентификатору CID тега IMG гипертекста. Такие вложения можно вставить на форму в текст или избавиться за ненадобностью, в 9 из 10 случаев такие вложения это логотипы компаний, или другая символика.
image007.jpg@01D9FD2B.0C901310" alt="<a href="mailto:cidimage001.jpg@01D96624.A1A23BC0">cidimage001.jpg@01D96624.A1A23BC0">
src=cid:image007.jpg@01D9FD2B.0C901310 CID Указывает на идентификатор такого вложения.
Для построения переписки необходимо знать «Родителя» для каждого письма, то есть то письмо, на которое полученное, является ответом. Ранее в теории описывались теги In-Reply-To и References именно из них мы и получим Message-ID предшествующего сообщения.
Для облегчения понимая общих принципов, Родителя письма будем записывать прямо в реквизит письма, в виде Message-ID. В рабочих базах оптимально использовать отдельный регистр связей для хранения ссылок на письма, это даст наибольший выигрыш в скорости при построении переписок.
Таким образом алгоритм записи письма будет выглядеть так:
Для построения переписки наиболее удобным и интуитивным вариантом будет ДревоЗначений совместно с рекурсивными процедурами.
На этом этапе нужно определиться с потребностями, получать древо переписки, или же ее ветку.
Почта 1 отправила сообщение на Почту 2 и Почту 3, после каждая почта отвечает на сообщение.
Далее Почта 1 ведет отдельную переписку с Почтой 2 и Почтой 3. В такой ситуации древо переписки будет выглядеть так:
Древо переписки
А ветка переписки для Почты 2 так:
Ветка переписки
Как основа построения древа берется корневое письмо. Это письмо с которого началась переписка. Рекурсивно проходя по родителям письма встретится сообщение без «родителя», оно и берется за корневое.
Узнав корневое письмо можно искать все ссылки на него. Получив массив сообщений, ссылающихся на него, повторяем процесс. То есть для каждого письма из массива ищем письма, ссылающиеся на них.
Для построения только ветки действия аналогичны. Сохраняя всех родителей одного за другим, а потом сохраняя все ветвления что были после исходного письма, строится ветка.
После разбора частного случая приступаем к созданию механизма получения почты с набора почт.
Для хранения данных о настройках подключения создадим справочник и внесем в него минимальную информацию:
1. Адрес электронной почты.
2. Пароль (Часто требуется именно внешний пароль для приложений)
3. Тип входящей почты.
4. Адрес входящей почты.
5. Адрес исходящей почты.
6. Порт входящей почты.
7. Порт исходящей почты.
8. Защищенное соединение входящей почты.
9. Защищенное соединение исходящей почты.
Построение ИнтренетПочтовогоПрофиля будет происходить по данным из этого справочника. Запросом получаем все нужные данные для подключения, в данном случае сохраняем их в структуру.
После отправляем в процедуру создания 1С ИнтренетПочтовогоПрофиля.
По каждому ИнтренетПочтовогоПрофилю получаем соединения, а затем и письма.
По окончанию прохождения цикла по всем Профилям сформируется общая таблица писем, работу с которой рассматривали ранее.
Такой подход имеет смысл при небольшом потоке писем. Так как все полученные данные, в том числе и вложения, до записи хранятся в быстрой памяти, это может оказаться критичным при получении огромного количество писем.
Выходом из положения может служить разбиение всех профилей на порции. К примеру 700 почт разделяются на порции по 70 штук в каждой. Такой подход будет выгодней как с точки зрения потребления ресурсов, так и защиты от ошибок.
Получив все нужные нам письма, приступаем к написанию алгоритма создания древа переписки. В первую очередь напишем процедуру поиска корневого письма. Основой таких алгоритмов являются рекурсивные функции и процедуры, такие функции работают как цикл, за счет вызова самих себя. При их написании следует быть осторожным, ведь можно случайно создать бесконечно зацикленную процедуру.
В документе каждого письма содержится идентификатор его «Родителя», поэтому нам нужно получать письма до тех пор, пока идентификатор не будет пустой.
После нужна процедура, которая будет искать все ссылки сначала на корневое письмо, потом ссылки на те письма, которые ссылаются на корневое письмо, и так далее.
В результате работы такой процедуры получаем полное древо переписки.
Полное древо переписки
Теперь кратко рассмотрим отправку писем, ранее было упомянуто о протоколе SMTP, пришло время поработать и с ним. Отправка сообщений устроена легче чем получение, неожиданностей тут меньше.
В первую очередь формируем ИнтернетПочтовоеСообщение, после методом Послать() отправляем его.
Формирование ИнтернетПочтовоеСообщение происходит с помощью заполнения его свойств. Свойства отправляемого письма идентичны свойствам получаемого, которые рассматривались в примере выше. Для заполнения каждого свойства в синтаксис помощнике представлена развернутая документация. В рамках этой статьи подробно рассматривать отправку писем не будем.
Советы:
Удалять вложения тела. Причина довольна проста, такие вложения будут занимать больше места чем обычные, но при этом не нести практически никакой ценности. Каждое сообщение будет приходить с изображениями предыдущих писем, а это геометрический рост занимаемой памяти. То есть при ведении переписки из 10 сообщений в каждом из которых есть логотип, общее количество скачанных изображений будет 55, из которых уникальны лишь 2.
In-Reply-To. При поиске этого заголовка подстрока часто находится как содержимое другого заголовка. Искать стоит “In-Reply-To: < “.
IMAP идентификаторы. Хранить идентификаторы лучше всего в отдельном регистре с двумя измерениями - Учетная запись, Идентификатор. Такой подход даст как выигрыш в скорости, так и надежность всего механизма загрузки. Сами идентификаторы записывать до обработки дублей.
Дубли исходящих писем. В случае раздельного хранения почты (Исходящая, Входящая) обрабатывать дубли исходящих писем не имеет смысла. Каждое письмо мог отправить только один человек, что уже гарантирует уникальность. Если исходящие письма дублируются, значит ошибка на этапе ее получения.
Получение почты за период. Для избавления от дублей еще на этапе получения почты используется метод ПолучитьИдентфиикаторы(), в который передаются все ранее полученные IMAP идентификаторы. Такой подход будет работать не всегда, в какой-то момент почтовый сервис выдаст ошибку о превышении количества отправляемых идентификаторов. Оптимальное решение получать письма за определённый период с помощью параметра отбора, например, за последнюю неделю, таким образом для каждой почты будет достаточно хранить и отправлять идентификаторы за последние 7 дней.
Статья направлена на разработку полностью собственной гибкой интеграции с почтовыми сервисами, поэтому подсистема электронных писем БСП в ней не рассматривается.
Вся информация прожитый мной опыт при разработке такого функционала, поэтому если вы только начали создание интеграции, статья сократит вам огромное количество времени на построение общей архитектуры и взаимодействия.
Специалист компании ООО "Кодерлайн"
Семен Чернов