Gal Ratner
Gal Ratner is a Techie who lives and works in Los Angeles CA and Austin TX. Follow galratner on Twitter Google
Send a web page as an email with embedded images

In email marketing, sending customized emails is a common practice. Emails containing special offers, shopping cart items or any other dynamic content, are usually tailored to the individual and are being sent either by request or as a part of a marketing campaign. This presents a challenge since email templates are usually being maintained by the marketing department and need to be simple to change, yet flexible enough for a programmer to add any dynamic data as needed. The solution can be reading a dynamic existing HTML or ASPX page and emailing a copy of it to the recipients.
This blog post will show how to read the HTML content of a page, add the images to an email and send the email and images as an embedded resource so that the recipients will not need to view images hosted on an external server.
First we need to read the page we are about to send. This can be a URL containing the customer’s email or unique ID for example: mydomain/salereceipt.aspx?SaleID=LKJGHIU

        /// <summary>
        /// Read the HTML from a URL
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        private static string GetPageHTML(string url)
        {
            string pageHTML = string.Empty;

            var request = (HttpWebRequest)WebRequest.Create(url);
            request.Timeout = 100000;
            using (var stream = request.GetResponse().GetResponseStream())
            {
                using (var reader = new StreamReader(stream))
                {
                    pageHTML = reader.ReadToEnd();
                }
            }

            return pageHTML;
        }

After we have the HTML we need to extract and replace all of the images with an embedded resource.

        /// <summary>
        /// Replace the source of all the images with cid:uniqueId, download the image and add to the LinkedResource collection.
        /// </summary>
        /// <param name="pageHTML"></param>
        /// <param name="images"></param>
        /// <param name="baseURL"></param>
        /// <returns></returns>
        private static List<LinkedResource> ProcessEmbeddedHTML(out string processedPageHTML, string pageHTML, List<string> images, string baseURL)
        {
            List<LinkedResource> resources = new List<LinkedResource>();
            foreach (string image in images)
            {
                string imageName = Guid.NewGuid().ToString().Replace("-"string.Empty);
                pageHTML = pageHTML.Replace(image, "cid:" + imageName);
                LinkedResource imagelink = GetLinkedResource(image, baseURL, imageName);
                resources.Add(imagelink);
            }
            processedPageHTML = pageHTML;
            return resources;
        }

        /// <summary>
        /// Create a new LinkedResource from an image stream
        /// </summary>
        /// <param name="imageURL"></param>
        /// <param name="baseURL"></param>
        /// <param name="imageName"></param>
        /// <returns></returns>
        private static LinkedResource GetLinkedResource(string imageURL, string baseURL, string imageName)
        {
            // Turn reletiv URLs to absolute URLs
            Uri imageURI = null;
            if (!Uri.TryCreate(imageURL, UriKind.Absolute, out imageURI))
                Uri.TryCreate(new Uri(baseURL), imageURL, out imageURI);

            if (imageURI == null)
                return null;

            MemoryStream memoryStream = null;
            LinkedResource imagelink = null;
            using (WebClient client = new WebClient())
            {
                byte[] myDataBuffer = client.DownloadData(imageURI);
                memoryStream = new MemoryStream(myDataBuffer);
                imagelink = new LinkedResource(memoryStream, client.ResponseHeaders["Content-Type"]);
            }

            imagelink.ContentId = imageName;
            imagelink.ContentLink = new Uri("cid:" + imageName);
            imagelink.TransferEncoding = System.Net.Mime.TransferEncoding.Base64;
            return imagelink;
        }

        /// <summary>
        /// Get a list of all the images in the HTML
        /// </summary>
        /// <param name="HTMLText"></param>
        /// <returns></returns>
        public static List<string> FindHTMLImages(string HTMLText)
        {
            string anchorPattern = @"(?<=img\s*\S*src\=[\x27\x22])(?<Url>[^\x27\x22]*)(?=[\x27\x22])";
            MatchCollection matches = Regex.Matches(HTMLText, anchorPattern, RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Compiled);
            List<string> imageSources = new List<string>();

            foreach (Match m in matches)
            {
                string url = m.Groups["Url"].Value;
                Uri testUri = null;
                if (Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out testUri))
                {
                    if (!imageSources.Exists(s => s == testUri.ToString()))
                        imageSources.Add(testUri.ToString());
                }
            }
            return imageSources;
        }

Finally we can send the email. For customers with email clients that do not display HTML we used a text version that simply links the email to the online page version.

        static void Main(string[] args)
        {
            SendEmailWithResources("http://www.invertedsoftware.com""Gal Ratner""myemail@mydomain.com""remail@rdomain.com");
            Console.WriteLine("Email Sent. Press Enter to terminate program.");
            Console.ReadLine();
        }

        /// <summary>
        /// Send a web page as an email
        /// </summary>
        /// <param name="url"></param>
        /// <param name="fromName"></param>
        /// <param name="fromEmail"></param>
        /// <param name="toEmail"></param>
        private static void SendEmailWithResources(string url, string fromName, string fromEmail, string toEmail)
        {
            try
            {
                string html = GetPageHTML(url);
                List<string> images = FindHTMLImages(html);
                string processedPageHTML;
                List<LinkedResource> linkedResources = ProcessEmbeddedHTML(out processedPageHTML, html, images, url);
                SendEmail(linkedResources, processedPageHTML, url, fromName, fromEmail, toEmail);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }

        private static void SendEmail(List<LinkedResource> linkedResources, string processedPageHTML, string messageURL, string fromName, string fromEmail, string toEmail)
        {
            using (MailMessage mail = new MailMessage())
            {
                mail.From = new MailAddress(fromEmail, fromName);
                mail.To.Add(toEmail);
                mail.Subject = "This email contains a web page";

                string txtBody = "See this email online here: " + messageURL;
                AlternateView plainView = AlternateView.CreateAlternateViewFromString(txtBody, null"text/plain");

                AlternateView htmlView = AlternateView.CreateAlternateViewFromString(processedPageHTML, null"text/html");
                foreach (LinkedResource linkedResource in linkedResources)
                    htmlView.LinkedResources.Add(linkedResource);

                mail.AlternateViews.Add(plainView);
                mail.AlternateViews.Add(htmlView);

                SmtpClient client = new SmtpClient("localhost");
                client.Send(mail);
            }
        }

Of course any style sheets need to be inline in the page and please remember that HTML may look different on email clients.

Shout it


Posted 27 Feb 2011 5:45 AM by Gal Ratner
Filed under:

Powered by Community Server (Non-Commercial Edition), by Telligent Systems