Разработка
01
Июл
18

Отправка Электронной почты с вложенными рисунками

Давнишняя проблема CRM – стандартными средствами нельзя создавать рассылку электропочты содержащую красиво оформленные (в том числе с помощью рисунков) сообщения. Частично с этим можно бороться с помощью добавления рисунков в HTML со ссылкой на общедоступный сервер, а сложного динамического форматирования можно добисться с помощью JavaScript, либо кастомного Бизнес-процесса. Но если конечный получатель не имеет доступа к общедоступному серверу (по любой причине), то вместо рисунков он увидит пустые квадратики! Поэтому это только наполовину решало проблему. С технической точки зрения можно создать сообщения электронной почты, включающие как само сообщение, так и использованные в нем рисунки (в виде аттачей). А чтобы такие рисунки корректно отбражались в сообщении (которое должно быть написано с помощью HTML), в нем необходимо использовать тег IMG такого вида:

<img src="cid:id_рисунка" />

где cid это уникальный идентификатор приаттаченого рисунка, который задается при формировании электронного сообщения.

Итак, возможность есть – реализуем! 🙂

Логика работы решения будет следующей:

  1. Юзвер создает самое обычное сообщение Электронной почты в CRM – задает отправителя, получателей и тему сообщения;
  2. Далее нужно добавить во вложения все рисунки, которые будут использоваться в птсьме. А в само тело сообщения, в то место (точнее во все места), где должен распологаться какой-либо рисунок, вставить полное название рисунка (т.е. вместе с расширением);
  3. Сохраняет запись, но не отправляет ее;
  4. Нажать на кастомную кнопку на панели инструментов – «Отправить суперэлектропочту» – откроется кастомное ASPX-приложение, которое создаст и отправит Ваше сообщение со вложенными рисунками с помщью .Net кода.

Начнем:

  • Откройте VS и создайте новый новый веб-сайт (ASP.NET);
  • Подключите к сайту стандартные SDK сборки;
  • Добавьте на страницу Default.aspx.cs такой код:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Net.Mail;
    using System.Net.Mime;
    using Microsoft.Crm.Sdk;
    using Microsoft.Crm.SdkTypeProxy;
    using Microsoft.Crm.Sdk.Query;
    using System.IO;
    using System.Text;
    using System.Collections.ObjectModel;
    
    public partial class _Default : System.Web.UI.Page 
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            // Получаем GUID Электронной почты из строки запроса
            if (Request.QueryString["id"] == null) return;
                string emailId = Request.QueryString["id"].Replace("{", "").Replace("}", "");
            
            // Конфигурируем CrmService
            CrmAuthenticationToken myToken = new CrmAuthenticationToken();
            myToken.OrganizationName = "superfirma";
            myToken.AuthenticationType = 0;
            CrmService service = new CrmService();
            service.Credentials = System.Net.CredentialCache.DefaultCredentials;
            service.Url = "http://win-n22hj23d1b1/MSCrmServices/2007/CrmService.asmx";
            service.CrmAuthenticationTokenValue = myToken;
    
            // Возвращаем из CRM необходимые поля Электронной почты
            ColumnSet cols = new ColumnSet();
            cols.Attributes.Add("from");
            cols.Attributes.Add("to");
            cols.Attributes.Add("cc");
            cols.Attributes.Add("bcc");
            cols.Attributes.Add("subject");
            cols.Attributes.Add("description");
            email email = (email)service.Retrieve("email", new Guid(emailId), cols);
            activityparty[] To = email.to;
            activityparty[] From = email.from;
            activityparty[] Cc = email.cc;
            activityparty[] Bcc = email.bcc;
    
            // Запрашиваем в CRM вложения Электронной почты, которые относятся к нужной записи
            QueryExpression query = new QueryExpression();
            query.EntityName = "activitymimeattachment";
    
            ColumnSet columns = new ColumnSet(); // Указываем какие поля вложения необходимо вернуть
            columns.Attributes.Add("body");
            columns.Attributes.Add("filename");
            columns.Attributes.Add("mimetype");
            query.ColumnSet = columns;
    
            ConditionExpression condition = new ConditionExpression();
            condition.AttributeName = "activityid";
            condition.Operator = ConditionOperator.Equal;
            condition.Values = new string[] { emailId }; // GUID нужной записи Электронной почты
    
            ConditionExpression condition2 = new ConditionExpression();
            condition2.AttributeName = "mimetype";
            condition2.Operator = ConditionOperator.In;
            condition2.Values = new string[] { "image/gif", "image/jpeg", "image/png" }; // Отбираем только вложения рисунков GIF, JPEG и PNG
    
            FilterExpression filter = new FilterExpression();
            filter.FilterOperator = LogicalOperator.And;
            filter.Conditions.Add(condition);
            filter.Conditions.Add(condition2);
            query.Criteria = filter;
    
            RetrieveMultipleRequest retrieve = new RetrieveMultipleRequest();
            retrieve.Query = query;
            RetrieveMultipleResponse retrieved = (RetrieveMultipleResponse)service.Execute(retrieve);
            BusinessEntityCollection attachments = retrieved.BusinessEntityCollection;
    
            // Создаем новое сообщение электронной почты
            MailMessage mailMessage = new MailMessage();
    
            foreach (var FromItem in From) // Заполняем поле От
            {
                MailAddress FromAddress = new MailAddress(FromItem.addressused.ToString(), FromItem.partyid.name);
                mailMessage.From = FromAddress;
            }
            foreach (var ToItem in To) // Заполняем поле Кому
            {
                MailAddress ToAddress = new MailAddress(ToItem.addressused.ToString(), ToItem.partyid.name);
                mailMessage.To.Add(ToAddress);
            }
            foreach (var CcItem in Cc) // Заполняем поле Копия
            {
                MailAddress CcAddress = new MailAddress(CcItem.addressused.ToString(), CcItem.partyid.name);
                mailMessage.CC.Add(CcAddress);
            }
            foreach (var BccItem in Bcc) // Заполняем поле Скрытая копия
            {
                MailAddress BccAddress = new MailAddress(BccItem.addressused.ToString(), BccItem.partyid.name);
                mailMessage.Bcc.Add(BccAddress);
            }
    
            mailMessage.Subject = email.subject; // Заполняем поле Тема
            string mailBody = email.description; // Получаем тело письма
    
            // Просматриваем все рисунки приложенные к письму
            Collection<LinkedResource> linkedCollection = new Collection<LinkedResource>();
            foreach (activitymimeattachment attachment in attachments.BusinessEntities)
            {
                // Если имя рисунка встречается в письме, то 
                if (mailBody.IndexOf(attachment.filename) != -1)
                {
                    // ... заменяем его на тег IMG
                    mailBody = mailBody.Replace(attachment.filename, "<img src=\"cid:" + attachment.filename + "\" />");
    
                    // ... и добавляем в коллекцию LinkedResource сам рисунок
                    byte[] fileBuffer = Convert.FromBase64String(attachment.body);
                    MemoryStream fileStream = new MemoryStream(fileBuffer);
                    LinkedResource img = new LinkedResource(fileStream, attachment.mimetype);
                    img.ContentId = attachment.filename;
                    linkedCollection.Add(img);
                }            
            }
            // Создаем объект AlternateView и прикрепляем к нему рисунки из коллекции linkedCollection
            AlternateView av = AlternateView.CreateAlternateViewFromString(mailBody, null, MediaTypeNames.Text.Html);
            foreach (LinkedResource lr in linkedCollection) {
                av.LinkedResources.Add(lr);
            }
            mailMessage.AlternateViews.Add(av);
            mailMessage.IsBodyHtml = true;
    
            // Настраиваем SMTP сервер
            SmtpClient mailSender = new SmtpClient("smtp.mail.ru", 2525);
            
            try
            {
                // Отправляем Электропочту
                mailSender.Send(mailMessage);
    
                // Выводим сообщение об успешной отправке
                Response.Clear();
                Response.Write("Сообщение отправлено");
            }
            catch (SmtpFailedRecipientException error)
            {
                Response.Clear();
                Response.Write("Ошибка: " + error.Message + "\nНе удалось отправить для: " + error.FailedRecipient);
            }
        }
    }
    

    Разбор полетов:

    • Вытаскиваем GUID Электронной почты из URL;
    • Настраиваем Crm Service (соответственно измените праметры на Ваши);
    • Запрашиваем по GUID’у поля Электронной почты;
    • Запрашиваем вложения Электронной почты (но только рисунки GIF, JPEG, PNG);
    • Создаем с помощью .Net кода новое сообщение электронной почты (экземпляр класса MailMessage);
    • Заполняем поля От, Кому, Копия, Скрытая копия;
    • Затем просматриваем все возвращенные вложения и:
      • Если имя рисунка содержится в теле сообщения, то заменяем его на тег IMG ссылающийся на CID равным имени рисунка;
      • Прикрепляем рисунок к письму и задаем для него CID равным имени рисунка.
    • Добавляем в сообщение электроннойпочты HTML код (полученный из тела сообщения Электронной почты CRM и измененной при просмотре вложений);
    • Настраиваем SMTP сервер (соответственно укажите свой – я использовал Mail.ru);
    • Отправляем электронную почту и выводим сообщение пользователю.
  • Перейдите Build — Publish Web Site — укажите путь к папке sendmail (ее Вам нужно предварительно создать), расположенную в папке ISV сайта CRM;
  • Выгрузите ISV.config и добавьте на форму элетропочты такую кнопку:
    <Button Icon="/_imgs/ico_18_debug.gif" JavaScript="
    	var width  = 400;
    	var height = 200;
    	var left   = (screen.width  - width)/2;
    	var top    = (screen.height - height)/2;
    	var params = &apos;width=&apos;+width+&apos;, height=&apos;+height;
    	params += &apos;, top=&apos;+top+&apos;, left=&apos;+left;
    	params += &apos;, directories=no&apos;;
    	params += &apos;, location=no&apos;;
    	params += &apos;, menubar=no&apos;;
    	params += &apos;, resizable=no&apos;;
    	params += &apos;, scrollbars=no&apos;;
    	params += &apos;, status=no&apos;;
    	params += &apos;, toolbar=no&apos;;
    	window.open(&apos;../../ISV/sendmail/Default.aspx?id=&apos; + crmForm.ObjectId,&quot;Суперэлектропочта&quot;, params);
    ">
    	<Titles>
    		<Title LCID="1049" Text="Отправить суперэлектропочту" />
    	</Titles>
    	<ToolTips>
    		<ToolTip LCID="1049" Text="Отправить суперэлектропочту" />
    	</ToolTips>
    </Button>
    

    Кнопка просто открывает нашу кастомную страницу и передает ей в URL GUID текущей записи Электронной почты;

  • Готово. Тестируем: создайте новыую запись Электронной почты (причем в получателях укажите адрес электронной почты, к которому можете получить доступ), приложите к ней рисунки, добавьте в тело сообщения имена рисунков (вместе с расширениями). Сохраните запись и нажмите кнопку «Отправить суперэлектропочту». Ну, и идите смотреть это письмо! 🙂

З.Ы. А если открыть HTML код полученного сообщения, то можете увидеть как рисунки ссылаются на CID.




Комментарии (18)
  • Vladislav Osmanov 01.07.2010

    Интересный способ. Можно попробовать упростить и ссылки на CID вставлять скриптом прямо в окно CRM.

    В качестве альтернативного метода отправки картинок ещё будет интересно рассмотреть способ с data URI scheme. Для такой реализации даже не потребуется прикреплять картинки к письму, т.к. изображения будут закодированы в base64 прямо в теле сообщения.

    Пример:
    Код элемента

    В письме будет выглядеть как:

  • slivka_83 01.07.2010

    Прикольно 🙂 нада будет как-нибудь попробовать 🙂

    П.С. движок блога убил Ваш код 🙂 Чтобы этого не происходило необходимо использовать BB теги

    [javascript]

    ,

    [csharp]

    🙂

  • Vladislav Osmanov 01.07.2010

    Собственно, код был с Википедии:

    <img src="data:image/png;base64,
    iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABGdBTUEAALGP
    C/xhBQAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9YGARc5KB0XV+IA
    AAAddEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIFRoZSBHSU1Q72QlbgAAAF1J
    REFUGNO9zL0NglAAxPEfdLTs4BZM4DIO4C7OwQg2JoQ9LE1exdlYvBBeZ7jq
    ch9//q1uH4TLzw4d6+ErXMMcXuHWxId3KOETnnXXV6MJpcq2MLaI97CER3N0
    vr4MkhoXe0rZigAAAABJRU5ErkJggg==" alt="Red dot" />
    
  • Ваня 01.07.2010

    Товарищь гуру, подскажите плиз, как при изменении значение атрибута (в моем случае атрибут в ввиде списка), формировалось автоматически письмо и отправлялось указанному в письме адресату.
    Спасибо!!

  • Vladislav Osmanov 01.07.2010

    Гуру стесняются, отвечу я : )
    Изобретать ничего не надо, вам необходимо Workflow-правило на изменение поля. С единственным шагом: Отправить эл. почту.

  • slivka_83 01.07.2010

    Гуру не стесняется 🙂 Гуру пишет «Войну и мир» 🙂 А в это время все так и наровят обидеть гуру 🙂

  • Alex 01.07.2010

    Добрый день, а можно использовать данный способ для массовой рассылки сообщений?

  • slivka_83 01.07.2010

    наверника можно 🙂 только этот вопрос нада исследовать 🙂

  • Макс 01.07.2010

    Добрый день, так что по поводу массовой рассылки? Я так понимаю нужно прописывать все смтп для каждого почтового сервиса, или нет?

  • slivka_83 01.07.2010

    Не совсем понял вопроса. Какие смтп вы собрались прописывать и где?

  • Макс 01.07.2010

    Строка 131, 132. Настройка смтп. А при массовой рассылке на разные почтовые серверы, как быть? прописывать каждый?

  • slivka_83 01.07.2010

    Ну, я Вашей ситуации не знаю. Но если они у Вас в каждом письме меняются. То можно завести специальные поля на форме электропочты, которые оператор будет заполнять. И они будут передаваться в стоке URL на страничку.

  • Макс 01.07.2010

    Ситуация такая, есть около 10000 контактов, естественно с разными адресами. По ним хочу провести рассылку, через бизнес-процесс, либо через маркетинговую компанию, не важно. Важно то, как быть с развёрнутыми изображениями в письме в таком случае =( ? Может у вас есть ещё какие либо варианты?

  • slivka_83 01.07.2010

    Ну, здесь Вам нужно применить какой-либо другой подход. Например настроить шаблон электропочты (по тому же принципу, что описан в статье) и по нему производить рассылку. А на событие preSend повесить плагин, который будет производить модификацию письма, описанную в статье.

    Тока все равно не понимаю причем тут SMTP сервер… он вполне может быть один.

  • Макс 01.07.2010

    Спасибо за ответы, с смтп вопрос отпадает, просто изначально немного не правильно Вас понял)
    Появилась другая проблема при создании плагина, скажите может я что то не правильно делаю?
    Создаю новый проект
    выбираю Class Library (Framework 3.5)
    Подключаю сборки: System.Web.dll, microsoft.crm.sdk.dll, microsoft.crm.sdktypeproxy.dll

    И получаю такие ошибки, http://clip2net.com/s/2Bt4r

    Подскажите пожалуйста, где я ошибся

  • slivka_83 01.07.2010

    Почему Framework 3.5? у Вас 4 CRM?

  • Макс 01.07.2010

    Да, 4. При выборе Framework 4.0 то же самое =(

  • slivka_83 01.07.2010

    Убедитесь что в свойствах проекта установлен .Net Framework 3.5, а не .Net Framework 3.5 Client Profile.

*

code