Images breaking when sending mail using SmtpClient
Asked Answered
W

3

8

I am sending a mail using C# using the SmtpClient class. I am doing the following things before sending the mail.

var mailMessage = new MailMessage();

model.ToAddresses.ForEach(to => mailMessage.To.Add(to));
mailMessage.Subject = "Test Email - By Yasser";

mailMessage.Body = String.Format("{0}{1}{2}",
                                    "<html><body>",
                                     GetEmailContent(model),
                                     "</body></html>");
mailMessage.IsBodyHtml = true;
return MailService.SendEmail(mailMessage);

and below is my MailService class:

public class MailService
{
    public static bool SendEmail(MailMessage mailMessage)
    {
        var smtpClient = new SmtpClient();
        try
        {
            smtpClient.Send(mailMessage);
            return true;
        }
        catch(Exception exp)
        {
            return false;
        }
    }
}

Now when I send the mail, the mail gets sent, here is what I get as the content of the mail in outlook when I press the view source. Below is the content of the email with view source (Obviously I have kept only a part of the image data)

<html>

<body>
    <h1>Test</h1>
    <h2>Hello World</h2>
    <h3>Missing close h3 tag</h3>

    <p>
        <a href="www.google.com">
            <img src="data:image/gif;base64,/9j/4AAQSkZJRgABAgEAYABgAAD/4Q8HRXhpZgAAT" />
        </a>
    </p>
</body>

</html>

So this appears broken(the images) in the mail, but when I copy this source and paste it into an editor and open the file using a browser all seems good (even the images).

Update : Added image of the mail from outlook

enter image description here

Any ideas ????

Westernism answered 24/5, 2013 at 14:31 Comment(7)
To clarify: Your problem is, that the images do not load (show up) in the email? Does it show an Icon, that the image could not be loaded?Knockabout
you may want to try how the mail looks in gmail or some other webmailer. To verify that the problem is for all mailclients and not only for outlook.Knockabout
How are you adding the image to the email? Are you using your own methods to get the string needed to embed an image or something built in like an AlternateView?Rufescent
you may want to look at this questions answers: #9110591Knockabout
@Anubis1233 tried in thunderbird and gmail, looks the same...Westernism
@Sean Thanks for letting me know about this AlteranteView will try it out and if it works will post an answer hereWesternism
@Yasser Great stuff, that's exactly what I was going to suggest if you weren't using it, it just automagically works =]Rufescent
W
16

This is what I tried and works for me, tested in outlook, thunderbird and gmail. WORKS FINE !

You might want to check out the following resources I referred to make this happen :

Sample Code :

// we need to use the prefix 'cid' in the img src value
string emailReadyHtml = string.empty;
emailReadyHtml += "<p>Hello World, below are two embedded images : </p>";
emailReadyHtml += "<img src=\"cid:yasser\" >";
emailReadyHtml += "<img src=\"cid:smile\" >";

MailMessage mailMessage = new MailMessage();

mailMessage.To.Add("[email protected]");
mailMessage.From = new MailAddress("[email protected]", "Info");

mailMessage.Subject = "Test Mail";
mailMessage.IsBodyHtml = true;

string image1Path = HttpContext.Current.Server.MapPath("~/Content/images/yasser.jpg");
byte[] image2Bytes = someArrayOfByte;

ContentType c = new ContentType("image/jpeg");

// create image resource from image path using LinkedResource class.
LinkedResource linkedResource1 = new LinkedResource(imagePath);
linkedResource1.ContentType = c ;
linkedResource1.ContentId = "yasser";
linkedResource1.TransferEncoding = TransferEncoding.Base64;

// the linked resource can be created from bytes also, which may be stored in database (which was my case)
LinkedResource linkedResource2 = new LinkedResource(new MemoryStream(image2Bytes));
linkedResource2.ContentType = c;
linkedResource2.ContentId = "smile";
linkedResource2.TransferEncoding = TransferEncoding.Base64;

AlternateView alternativeView = AlternateView.CreateAlternateViewFromString(emailReadyHtml, null, MediaTypeNames.Text.Html);

alternativeView.ContentId = "htmlView";
alternativeView.TransferEncoding = TransferEncoding.SevenBit;

alternativeView.LinkedResources.Add(linkedResource1) ;
alternativeView.LinkedResources.Add(linkedResource2);

mailMessage.AlternateViews.Add(alternativeView);

SmtpClient smtpClient = new SmtpClient();
smtpClient.Send(mailMessage);
Westernism answered 27/5, 2013 at 7:50 Comment(2)
You never assign the body to the mailMessage. This code does not work.Elephantiasis
Did you try the code @AAlferez? It's probably best to assign a body, but it's not required.Vivisect
V
3

Here is a function that will fix the problem. Call it before you SendEmail like:

ProcessEmbeddingImages(mailMessage);
return MailService.SendEmail(mailMessage);

The ProcessEmbeddingImages function is written in VB.Net so you might want to translate it to c# using one of those online translators.

Private Sub ProcessEmbeddingImages(ByRef oMail As System.Net.Mail.MailMessage)
    Dim oLinkedResources As New Hashtable()
    oMail.Body = PadSrcDataImage(oMail.Body, oLinkedResources)
    If oLinkedResources.Count > 0 Then

        Dim oAlternateView As System.Net.Mail.AlternateView = System.Net.Mail.AlternateView.CreateAlternateViewFromString(oMail.Body, Nothing, "text/html")
        oAlternateView.ContentId = "htmlView"
        oAlternateView.TransferEncoding = Net.Mime.TransferEncoding.SevenBit

        For Each oItem As DictionaryEntry In oLinkedResources
            Dim oKey As String() = Split(oItem.Key, "-")
            Dim i As Integer = oKey(0)
            Dim sExt As String = oKey(1)
            Dim sData As String = oItem.Value

            Dim oLinkedResource As New System.Net.Mail.LinkedResource(New IO.MemoryStream(System.Convert.FromBase64String(sData)))
            oLinkedResource.ContentId = "MyImg" & i
            oLinkedResource.TransferEncoding = Net.Mime.TransferEncoding.Base64
            oLinkedResource.ContentType = New System.Net.Mime.ContentType("image/" & sExt)
            oAlternateView.LinkedResources.Add(oLinkedResource)
        Next

        oMail.AlternateViews.Add(oAlternateView)
    End If
End Sub

Private Function PadSrcDataImage(ByVal sHtml As String, ByRef oLinkedResources As Hashtable) As String

    Dim oList As New ArrayList
    Dim sSearch As String = "\ssrc=['""]data:image/(.*?);base64,(.*?)['""]"

    Dim oMatches As System.Text.RegularExpressions.MatchCollection = System.Text.RegularExpressions.Regex.Matches(sHtml, sSearch, System.Text.RegularExpressions.RegexOptions.IgnoreCase)
    For Each m As System.Text.RegularExpressions.Match In oMatches
        If m.Groups.Count >= 2 Then
            Dim sExt As String = m.Groups(1).Value
            Dim sData As String = m.Groups(2).Value

            If sData.Length > 7 AndAlso Right(sData, 6) = "%3D%3D" Then
                'Replace trailing %3D%3D with ==
                sData = Left(sData, sData.Length - 6) & "=="
            End If

            oLinkedResources.Add(oLinkedResources.Count & "-" & sExt, sData)

            Dim iPos1 As Integer = m.Groups(1).Index - "data:image/".Length
            Dim iPos2 As Integer = m.Groups(2).Index + m.Groups(2).Length
            Dim iPoints As Integer() = {iPos1, iPos2}
            oList.Add(iPoints)
        End If
    Next

    Dim sRet As String = ""

    If oList.Count = 1 Then 'One img
        Dim iPoints As Integer() = oList(0)
        Dim sStr1 As String = sHtml.Substring(0, iPoints(0))
        Dim sStr2 As String = sHtml.Substring(iPoints(1))
        sRet = sStr1 & "cid:MyImg0" & sStr2

    ElseIf oList.Count > 1 Then

        For i As Integer = 0 To oList.Count - 1
            Dim iPoints As Integer() = oList(i)
            Dim iPos1 As Integer = iPoints(0)

            If i = 0 Then
                'First img
                Dim sStr1 As String = sHtml.Substring(0, iPos1)
                sRet += sStr1 & "cid:MyImg" & i

            Else 'Rest imgs
                Dim iPrevPos2 As Integer = oList(i - 1)(1)
                Dim sStr1 As String = sHtml.Substring(iPrevPos2, iPos1 - iPrevPos2)
                sRet += sStr1 & "cid:MyImg" & i

                If i = oList.Count - 1 Then    'Last
                    sRet += sHtml.Substring(iPoints(1))
                End If
            End If
        Next
    End If

    If sRet <> "" Then
        Return sRet
    Else
        Return sHtml
    End If
End Function
Vella answered 10/8, 2014 at 8:7 Comment(1)
It reads data:image data and adds these images as Linked Resources.Vella
P
0

To go along with Igor's Visual Basic Response, here is it converted to C#

   static void ProcessEmbeddingImages(ref MailMessage oMail)
    {
        Hashtable oLinkedResources = new Hashtable();
        oMail.Body = PadSrcDataImage(oMail.Body, ref oLinkedResources);
        if (oLinkedResources.Count > 0)
        {
            AlternateView oAlternateView = AlternateView.CreateAlternateViewFromString(oMail.Body, null, "text/html");
            oAlternateView.ContentId = "htmlView";
            oAlternateView.TransferEncoding = System.Net.Mime.TransferEncoding.SevenBit;

            foreach (DictionaryEntry oItem in oLinkedResources)
            {
                string[] oKey = ((string)oItem.Key).Split('-');
                int i = int.Parse(oKey[0]);
                string sExt = oKey[1];
                string sData = (string)oItem.Value;

                LinkedResource oLinkedResource = new LinkedResource(new System.IO.MemoryStream(Convert.FromBase64String(sData)));
                oLinkedResource.ContentId = "MyImg" + i;
                oLinkedResource.TransferEncoding = System.Net.Mime.TransferEncoding.Base64;
                oLinkedResource.ContentType = new System.Net.Mime.ContentType("image/" + sExt);
                oAlternateView.LinkedResources.Add(oLinkedResource);
            }

            oMail.AlternateViews.Add(oAlternateView);
        }
    }

    static string PadSrcDataImage(string sHtml, ref Hashtable oLinkedResources)
    {
        ArrayList oList = new ArrayList();
        string sSearch = @"\ssrc=['""]data:image/(.*?);base64,(.*?)['""]";

        MatchCollection oMatches = Regex.Matches(sHtml, sSearch, RegexOptions.IgnoreCase);
        foreach (Match m in oMatches)
        {
            if (m.Groups.Count >= 2)
            {
                string sExt = m.Groups[1].Value;
                string sData = m.Groups[2].Value;

                if (sData.Length > 7 && sData.EndsWith("%3D%3D"))
                {
                    // Replace trailing %3D%3D with ==
                    sData = sData.Substring(0, sData.Length - 6) + "==";
                }

                oLinkedResources.Add(oLinkedResources.Count + "-" + sExt, sData);

                int iPos1 = m.Groups[1].Index - "data:image/".Length;
                int iPos2 = m.Groups[2].Index + m.Groups[2].Length;
                int[] iPoints = { iPos1, iPos2 };
                oList.Add(iPoints);
            }
        }

        string sRet = "";

        if (oList.Count == 1) // One img
        {
            int[] iPoints = (int[])oList[0];
            string sStr1 = sHtml.Substring(0, iPoints[0]);
            string sStr2 = sHtml.Substring(iPoints[1]);
            sRet = sStr1 + "cid:MyImg0" + sStr2;
        }
        else if (oList.Count > 1)
        {
            for (int i = 0; i < oList.Count; i++)
            {
                int[] iPoints = (int[])oList[i];
                int iPos1 = iPoints[0];

                if (i == 0)
                {
                    // First img
                    string sStr1 = sHtml.Substring(0, iPos1);
                    sRet += sStr1 + "cid:MyImg" + i;
                }
                else // Rest imgs
                {
                    int iPrevPos2 = ((int[])oList[i - 1])[1];
                    string sStr1 = sHtml.Substring(iPrevPos2, iPos1 - iPrevPos2);
                    sRet += sStr1 + "cid:MyImg" + i;

                    if (i == oList.Count - 1) // Last
                    {
                        sRet += sHtml.Substring(iPoints[1]);
                    }
                }
            }
        }

        if (!string.IsNullOrEmpty(sRet))
        {
            return sRet;
        }
        else
        {
            return sHtml;
        }
    }
Pinkard answered 24/8, 2023 at 18:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.