17

I'm writing a program that sends emails to users with multiple images (charts) embedded in the Email message body (HTML).

When I tried the sample located here..which worked well when I have to embed only one image http://www.systemnetmail.com/faq/4.4.aspx.

But, when i tried to embed multiple images using the below code, none of the images are being embedded , instead they are sent as attachments.

public MailMessage MailMessage(Metric metric, DateTime date)
{
    MailMessage msg = new MailMessage();
    msg.From = new MailAddress("test@gmail.com", "User1");
    msg.To.Add(new MailAddress("test@gmail.com"));
    msg.Subject = "Trend for metric: " + metric.Name;
    msg.IsBodyHtml = true;

    // Generate the charts for the given metric
    var charts = this.GenerateCharts(metric, date);
    int i = 0;
    string htmlBody = "<html><body>";
    List<LinkedResource> resources = new List<LinkedResource>();
    foreach (var chart in charts)
    {
        string imageTag = string.Format("<img src=cid:chart{0} /><br>", i);
        htmlBody += imageTag;
        LinkedResource graph = new LinkedResource(chart.Value, "image/jpeg");
        graph.ContentId = "chart" + i;
        resources.Add(graph);
        i++;
    }

    htmlBody += "</body></html>";

    // Alternate view for embedded images
    AlternateView avText = AlternateView.CreateAlternateViewFromString(metric.Name, null, MediaTypeNames.Text.Html);
    AlternateView avImages = AlternateView.CreateAlternateViewFromString(htmlBody, null, MediaTypeNames.Text.Html);

    // Add all the images as linked resources
    resources.ForEach(x => avImages.LinkedResources.Add(x));

    // Add the views for image
    msg.AlternateViews.Add(avText);
    msg.AlternateViews.Add(avImages);


    return msg;
}

Any clues as what I'm missing? I checked the .htm file which is also sent as attachment with the email, and html source looks as follows:

<html>><body><img src=cid:chart0 /><br><img src=cid:chart1 /><br><img src=cid:chart2/><br><img src=cid:chart3 /><br><img src=cid:chart4 /><br></body></html>

So the Q is how to send multiple images in the html body , not as attachment.

John Saunders
  • 160,644
  • 26
  • 247
  • 397
user330612
  • 2,189
  • 7
  • 33
  • 64
  • 1
    possible duplicate of [Sending an email with an image embedded in the body from C#](http://stackoverflow.com/questions/1921275/sending-an-email-with-an-image-embedded-in-the-body-from-c) – Tim Lloyd Aug 13 '11 at 18:23
  • Check this link. it has ready to use method for multiple inline attachment as well as for general attachment for pdf/excel files. https://stackoverflow.com/questions/33665280/add-multiple-images-in-the-email-body-inlineusing-c-sharp-windows-application/49329461#49329461 – kumar chandraketu Mar 16 '18 at 21:13

7 Answers7

24

The other way to embed images in E-mail when using System.Net.Mail is

Attach image from local drive to email and assign a contentID to it and later use this contentID in the image URL.

This can be done by:

var contentID = "Image";
var inlineLogo = new Attachment(@"C:\Desktop\Image.jpg");
inlineLogo.ContentId = contentID;
inlineLogo.ContentDisposition.Inline = true;
inlineLogo.ContentDisposition.DispositionType = DispositionTypeNames.Inline;

msg.IsBodyHtml = true;
msg.Attachments.Add(inlineLogo);
msg.Body = "<htm><body> <img src=\"cid:" + contentID + "\"> </body></html>";
jgauffin
  • 99,844
  • 45
  • 235
  • 372
Jagan
  • 665
  • 6
  • 5
  • Please done't post the exact same answer to multiple questions: it's either not a good fit for all or the questions are duplicates which should be flagged/closed as such. – kleopatra Oct 02 '13 at 14:41
  • 2
    Your method worked for me.I tried linked resources, but it didn't work. Almost everywhere I look, everyone seem to have got the images shown in their email clients using linked resources. Weird! – Romesh D. Niriella Jul 04 '14 at 07:38
  • 2
    Works perfectly. I don't really understand the need to work with linked resources. This is cleaner and more elegant in my taste. – Shy Agam Nov 09 '16 at 15:56
6

First, you could try to use absolute URIs to embedded images. Here is example from RFC-2557:

  From: foo1@bar.net
  To: foo2@bar.net
  Subject: A simple example
  Mime-Version: 1.0
  Content-Type: multipart/related; boundary="boundary-example";
          type="text/html"; start="<foo3@foo1@bar.net>"

  --boundary-example
  Content-Type: text/html;charset="US-ASCII"
  Content-ID: <foo3@foo1@bar.net>

  ... text of the HTML document, which might contain a URI
  referencing a resource in another body part, for example
  through a statement such as:
  <IMG SRC="http://www.ietf.cnri.reston.va.us/images/ietflogo.gif" ALT="IETF logo">

  --boundary-example
  Content-Location:
     http://www.ietf.cnri.reston.va.us/images/ietflogo.gif
  Content-Type: IMAGE/GIF
  Content-Transfer-Encoding: BASE64

  R0lGODlhGAGgAPEAAP/////ZRaCgoAAAACH+PUNvcHlyaWdodCAoQykgMTk5
  NSBJRVRGLiBVbmF1dGhvcml6ZWQgZHVwbGljYXRpb24gcHJvaGliaXRlZC4A
  etc...

  --boundary-example--

You just need to assign LinkedResource.ContentLink property instead of ContentId.

Second, you could embed images directly to your html with the "data" URL scheme.

    <IMG
    SRC="data:image/gif;base64,R0lGODdhMAAwAPAAAAAAAP///ywAAAAAMAAw
    AAAC8IyPqcvt3wCcDkiLc7C0qwyGHhSWpjQu5yqmCYsapyuvUUlvONmOZtfzgFz
    ByTB10QgxOR0TqBQejhRNzOfkVJ+5YiUqrXF5Y5lKh/DeuNcP5yLWGsEbtLiOSp
    a/TPg7JpJHxyendzWTBfX0cxOnKPjgBzi4diinWGdkF8kjdfnycQZXZeYGejmJl
    ZeGl9i2icVqaNVailT6F5iJ90m6mvuTS4OK05M0vDk0Q4XUtwvKOzrcd3iq9uis
    F81M1OIcR7lEewwcLp7tuNNkM3uNna3F2JQFo97Vriy/Xl4/f1cf5VWzXyym7PH
    hhx4dbgYKAAA7"
    ALT="Larry">

BTW, your html markup is not well-formed. You may also be interested in “foreach” vs “ForEach”

artplastika
  • 1,972
  • 2
  • 19
  • 38
6

So, I think figured out what the actual problem is Its in this line

// Alternate view for embedded images
    AlternateView avText = AlternateView.CreateAlternateViewFromString(metric.Name, null, MediaTypeNames.Text.Html);
    AlternateView avImages = AlternateView.CreateAlternateViewFromString(htmlBody, null, MediaTypeNames.Text.Html);

As you can see, both my views are specified as Text.Html, so the the 1st one is overriding the next one and so I only see text and images are sent as attachments

I made the following change and it worked as expected

AlternateView avText = AlternateView.CreateAlternateViewFromString(metric.Name, null, **MediaTypeNames.Text.Plain**);
AlternateView avImages = AlternateView.CreateAlternateViewFromString(htmlBody, null, MediaTypeNames.Text.Html);
user330612
  • 2,189
  • 7
  • 33
  • 64
  • I had the exact problem. This was exactly what I was looking for! However, if I ever end up using more than 2 images, I'm not entirely sure how to create each AlternateView. I'm assuming I'll be able to make all of them but the last as Text.Plain – roshambo Aug 29 '14 at 19:17
5

My alternatie:

First of all, a little extension:

public static class RegexExtensions
{
    public static string GetPattern(this IEnumerable<string> valuesToSearch)
    {
        return string.Format("({0})", string.Join("|", valuesToSearch));
    }
}

then get image names from folder:

    private string[] GetFullNamesOfImages()
    {
        string images = Path.Combine(_directoryName, "Images");
        if (!Directory.Exists(images))
            return new string[0];
        return Directory.GetFiles(images);
    }

then replacing image names by cid:

    private string InsertImages(string body)
    {
        var images = GetFullNamesOfImages().Select(Path.GetFileName).ToArray();
        return Regex.Replace(body, "(Images/)?" + images.GetPattern(), "cid:$2", RegexOptions.IgnoreCase | RegexOptions.Compiled);
    }

where body - is HTML body, and for example, <img src="Images/logo_shadow.png" alt="" style="width: 100%;" /> will be replaced by <img src="cid:logo_shadow.png" alt="" style="width: 100%;" />

then last action: adding images itselfs to mail:

    private MailMessage CreateMail(SmtpClient smtp, string toAddress, string body)
    {
        var images = GetFullNamesOfImages();

        string decodedBody = WebUtility.HtmlDecode(body);
        var text = AlternateView.CreateAlternateViewFromString(decodedBody, null, MediaTypeNames.Text.Plain);
        var html = AlternateView.CreateAlternateViewFromString(body, null, MediaTypeNames.Text.Html);
        foreach (var image in images)
        {
            html.LinkedResources.Add(new LinkedResource(image, new ContentType("image/png"))
                                     {
                                         ContentId = Path.GetFileName(image)
                                     });
        }


        var credentials = (NetworkCredential) smtp.Credentials;

        var message = new MailMessage(new MailAddress(credentials.UserName), new MailAddress(toAddress))
                      {
                          Subject = "Some subj",
                          Body = decodedBody
                      };
        message.AlternateViews.Add(text);
        message.AlternateViews.Add(html);
        return message;
    }
Alex Zhukovskiy
  • 9,565
  • 11
  • 75
  • 151
3

if you have the images online, meaning sending from a hosted site, i suggest you just reference those images simply by putting their url in the src.

<!-- using artplastika  examples -->
<IMG SRC="http://www.ietf.cnri.reston.va.us/images/ietflogo.gif" ALT="IETF logo" />

most of the newsletters use this method, and i believe it's lighter and can consume less resources than embedding itself.

hope this helps

black sensei
  • 6,528
  • 22
  • 109
  • 188
2
        AlternateView avHtml = AlternateView.CreateAlternateViewFromString(body, null, MediaTypeNames.Text.Html);
        LinkedResource inline = new LinkedResource(System.Web.HttpContext.Current.Server.MapPath("~/Images/e1.jpg"), MediaTypeNames.Image.Jpeg);
        inline.ContentId = "1";
        inline.TransferEncoding = System.Net.Mime.TransferEncoding.Base64;
        avHtml.LinkedResources.Add(inline);

        LinkedResource inline1 = new LinkedResource(System.Web.HttpContext.Current.Server.MapPath("~/CImages/2.jpg"), MediaTypeNames.Image.Jpeg);
        inline1.ContentId = "2";
        inline1.TransferEncoding = System.Net.Mime.TransferEncoding.Base64;
        avHtml.LinkedResources.Add(inline1);

        LinkedResource inline2 = new LinkedResource(System.Web.HttpContext.Current.Server.MapPath("~/Images/3.jpg"), MediaTypeNames.Image.Jpeg);
        inline2.ContentId = "3";
        inline2.TransferEncoding = System.Net.Mime.TransferEncoding.Base64;
        avHtml.LinkedResources.Add(inline2);

        LinkedResource inline3 = new LinkedResource(System.Web.HttpContext.Current.Server.MapPath("~/Content/Images/4.jpg"), MediaTypeNames.Image.Jpeg);
        inline3.ContentId = "4";
        inline3.TransferEncoding = System.Net.Mime.TransferEncoding.Base64;
        avHtml.LinkedResources.Add(inline3);

        MailMessage mail = new MailMessage();
        mail.AlternateViews.Add(avHtml);

HTML:

       <img src="cid:1" alt="" />
       <img src="cid:2" alt="" />
       <img src="cid:3" alt="" /`
       <img src="cid:4" alt="" />
jAC
  • 5,195
  • 6
  • 40
  • 55
Anup Shetty
  • 571
  • 8
  • 10
0

Try the following:

private static ICollection<LinkedResource> GetLinkedResources()
{
    var linkedResources = new List<LinkedResource>();

    linkedResources.Add(new LinkedResource(@"imagepath")
    {
        ContentId = "HeaderId",
        TransferEncoding = TransferEncoding.Base64
    });

    linkedResources.Add(new LinkedResource(@"imagepath")
    {
        ContentId = "MapId",
        TransferEncoding = TransferEncoding.Base64
    });

    return linkedResources;
}

Then you can call the method as follows:

 mailMessage.AlternateViews.Add(GetEmbeddedImage(body));
ivcubr
  • 1,988
  • 9
  • 20
  • 28