J_D
J_D

Reputation: 308

Calendar Invite with HTML body that contains images

I am having trouble getting my images to display as part of the HTML body of a mailmessage that also contains a calendar attachment (iCal).

What I am attempting to do is: Send a calendar invite which is recognized by outlook and also contains an HTML body (also displayed in Outlook 2013). That HTML should display a header and footer image inline.

My code works when viewed in gmail, but it will not display the images in Outlook - instead it shows the "header.jpg" and "footer.jpg" in the location of the body where I want the images actually displayed.

I've tried a lot of modifications to this code base - either the calendar invite portion is ignored (and then the images work), or the calendar invite portion works and the images are not displayed but replaced by the "header.jpg" and "screen.jpg" files that open fine when double-clicked. Can I get around this and display the images inline in Outlook?

Here is the code I am working with (snipped irrelevant stuff):

var header = new LinkedResource("header.jpg", MediaTypeNames.Image.Jpeg);
header.ContentId = Guid.NewGuid().ToString();
header.TransferEncoding = TransferEncoding.Base64;
header.ContentType = new ContentType("image/jpg");
header.ContentType.Name = "header.jpg";
header.ContentLink = new Uri(string.Format("cid:{0}", header.ContentId));

var screen = new LinkedResource("screen.jpg", MediaTypeNames.Image.Jpeg);
screen.ContentId = Guid.NewGuid().ToString();
screen.TransferEncoding = TransferEncoding.Base64;
screen.ContentType = new ContentType("image/jpg");
screen.ContentType.Name = "screen.jpg";
screen.ContentLink = new Uri(string.Format("cid:{0}", screen.ContentId));


var sb = new System.Text.StringBuilder();

var dtStart = appointmentStart;
var dtEnd = appointmentEnd;

var htmlcontent = string.Format("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">" +
    "<HTML><HEAD><META http-equiv=Content-Type content=\"multipart/alternative; charset=iso-8859-1\"></HEAD>" +
    "<BODY>" +
    "<img src=\"cid:" + header.ContentId + "\"/>" +
    "<div><strong>The bold works fine, but the header and footer don't work! /strong></div>" +
   "<br />" +
   "<div><img src=\"cid:" + screen.ContentId + "\"/></div>" +
   "</BODY></HTML>"
   );

var plaincontent = String.Format("snip snip");

var av1 = AlternateView.CreateAlternateViewFromString(htmlcontent, new ContentType("text/html"););
av1.LinkedResources.Add(header);
av1.LinkedResources.Add(screen);

sb.AppendLine("BEGIN:VCALENDAR");
sb.AppendLine("VERSION:2.0");
sb.AppendLine("METHOD:REQUEST");
sb.AppendLine("BEGIN:VEVENT");
sb.AppendLine(request.ContactEmail);
sb.AppendLine("CLASS:PUBLIC");
sb.AppendLine(string.Format("CREATED:{0:yyyyMMddTHHmmss}", DateTime.Now));
sb.AppendLine("DESCRIPTION:" + plaincontent);
sb.AppendLine("X-ALT-DESC;FMTTYPE=text/html:" + htmlcontent);
sb.AppendLine(string.Format("DTSTART:{0:yyyyMMddTHHmmssZ}", dtStart.ToUniversalTime()));
sb.AppendLine(string.Format("DTEND:{0:yyyyMMddTHHmmssZ}", dtEnd.ToUniversalTime()));
sb.AppendLine(string.Format("DTSTAMP:{0:yyyyMMddTHHmmssZ}", DateTime.Now.ToUniversalTime()));
sb.AppendLine("ORGANIZER;CN=\"TheOrganizer\":mailto:" + "[email protected]");
sb.AppendLine("SEQUENCE:0");
sb.AppendLine("UID:" + request.EmailNotificationUniqueId);
sb.AppendLine("LOCATION:" + request.Location + " : " + request.LocationInformation);
sb.AppendLine("SUMMARY;LANGUAGE=en-us:" + "removed");
sb.AppendLine("BEGIN:VALARM");
sb.AppendLine("TRIGGER:-PT1440M");
sb.AppendLine("ACTION:DISPLAY");
sb.AppendLine("DESCRIPTION:Reminder");
sb.AppendLine("END:VALARM");
sb.AppendLine("END:VEVENT");
sb.AppendLine("END:VCALENDAR");

var icsView = AlternateView.CreateAlternateViewFromString(sb.ToString(), new ContentType("text/calendar"));

var message = new MailMessage();

message.AlternateViews.Add(body);
message.AlternateViews.Add(icsView);

return message;

Is there something fundamentally wrong? This seems like it should be pretty simple and straightforward. I've done this plenty of times with normal email messages, but it seems outlook doesn't like the .ics attachment with images in the body of the HTML alternative view. The HTML body works fine (the shows bolded) but the darn embedded images just won't show.

I've tried manually encoding the images using base64 and that didn't help at all. I've tried with and without the HTML header tags in the htmlContent var, and that didn't make any difference either.

This shouldn't be this difficult - what am I missing?

EDIT: Here is the message source in google. This works fine in the google browser window (displays properly.) Unfortunately in Outlook the message does not work... Maybe the exchange server is supressing the images for internall messages? Is that even a thing?

Delivered-To: [email protected]
Received: by 10.182.167.74 with SMTP id zm10csp2847908obb;
        Fri, 7 Oct 2016 15:42:14 -0700 (PDT)
X-Received: by 10.37.171.105 with SMTP id u96mr17483671ybi.63.1475880134797;
        Fri, 07 Oct 2016 15:42:14 -0700 (PDT)
Return-Path: <[email protected]>
Received: from msg12.snip.com (msg12.snip.com. [192.195.66.28])
        by mx.google.com with ESMTPS id q11si2985157ywc.340.2016.10.07.15.42.14
        for <[email protected]>
        (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);
        Fri, 07 Oct 2016 15:42:14 -0700 (PDT)
Received-SPF: pass (google.com: domain of [email protected] designates 192.195.66.28 as permitted sender) client-ip=192.195.66.28;
Authentication-Results: mx.google.com;
       spf=pass (google.com: domain of [email protected] designates 192.195.66.28 as permitted sender) [email protected]
Received: from int11.snip.pvt (int11.snip.pvt [153.6.62.222]) by msg12.snip.com (Sentrion-MTA-4.3.1/Sentrion-MTA-4.2.2) with ESMTP id u97MgC5a022486 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=FAIL) for <[email protected]>; Fri, 7 Oct 2016 22:42:12 GMT
Received: from snip.com (snip.com [snip]) by int11.snip.pvt (Sentrion-MTA-4.3.1/Sentrion-MTA-4.2.2) with ESMTP id u97MgBAv016400 for <[email protected]>; Fri, 7 Oct 2016 22:42:12 GMT
Received: from snip.com with Microsoft SMTPSVC(7.5.7601.17514);
     Fri, 7 Oct 2016 18:42:12 -0400
MIME-Version: 1.0
From: snip <[email protected]>
To: [email protected]
Date: 7 Oct 2016 18:42:12 -0400
Content-Type: multipart/alternative; boundary=--boundary_0_8278962b-71cf-4ca1-9a64-5fb9629f2042
Message-ID: <[email protected]>
X-OriginalArrivalTime: 07 Oct 2016 22:42:12.0895 (UTC) FILETIME=[0BC44AF0:01D220EC]
X-Flow-Control: Sendmail Flow Controller v2.2.5 int11.snip.pvt u97MgBAv016400
X-Flow-Control-Info: class=Default rcpts=1 size=37636

----boundary_0_8278962b-71cf-4ca1-9a64-5fb9629f2042
Content-Type: multipart/related; boundary=--boundary_1_306bb8e7-fb92-4eab-bf5c-b7393cf499ac; type="text/html"

----boundary_1_306bb8e7-fb92-4eab-bf5c-b7393cf499ac
Content-Type: text/html
Content-Transfer-Encoding: quoted-printable

<img src=3D"cid:443ba735-6376-45a1-a5af-6c679321baa2"/><div><strong>The bol=
d works fine, but the header and footer don't work!</strong></div><br /><di=
v><img src=3D"cid:145c43b0-da9e-40c4-b149-3a149fbc4503"/></div>
----boundary_1_306bb8e7-fb92-4eab-bf5c-b7393cf499ac
Content-Type: image/jpeg
Content-Transfer-Encoding: base64
Content-ID: <443ba735-6376-45a1-a5af-6c679321baa2>


----boundary_1_306bb8e7-fb92-4eab-bf5c-b7393cf499ac
Content-Type: image/jpeg
Content-Transfer-Encoding: base64
Content-ID: <145c43b0-da9e-40c4-b149-3a149fbc4503>


----boundary_1_306bb8e7-fb92-4eab-bf5c-b7393cf499ac--
----boundary_0_8278962b-71cf-4ca1-9a64-5fb9629f2042
Content-Type: text/calendar
Content-Transfer-Encoding: base64

QkVHSU46VkNBTEVOREFSDQpWRVJTSU9OOjIuMA0KTUVUSE9EOlJFUVVFU1QNCkJFR0lOOlZFVkVO
VA0KSmRyYWhsQGdtYWlsLmNvbQ0KQ0xBU1M6UFVCTElDDQpDUkVBVEVEOjIwMTYxMDA3VDE4NDIx
MQ0KREVTQ1JJUFRJT046DQpYLUFMVC1ERVNDO0ZNVFRZUEU9dGV4dC9odG1sOjxpbWcgc3JjPSJj
aWQ6NDQzYmE3MzUtNjM3Ni00NWExLWE1YWYtNmM2NzkzMjFiYWEyIi8+PGRpdj48c3Ryb25nPlRo
ZSBib2xkIHdvcmtzIGZpbmUsIGJ1dCB0aGUgaGVhZGVyIGFuZCBmb290ZXIgZG9uJ3Qgd29yayE8
L3N0cm9uZz48L2Rpdj48YnIgLz48ZGl2PjxpbWcgc3JjPSJjaWQ6MTQ1YzQzYjAtZGE5ZS00MGM0
LWIxNDktM2ExNDlmYmM0NTAzIi8+PC9kaXY+DQpEVFNUQVJUOjIwMTYxMDI0VDE0MDAwMFoNCkRU
RU5EOjIwMTYxMDI0VDE4MDAwMFoNCkRUU1RBTVA6MjAxNjEwMDdUMjI0MjExWg0KT1JHQU5JWkVS
O0NOPSJEaXNuZXkgUmVmcmVzaCBUZWFtIjptYWlsdG86RGlzbmV5LlJlZnJlc2guVGVhbUBkaXNu
ZXkuY29tDQpTRVFVRU5DRTowDQpVSUQ6NGE5YjczNWQtOTQwYS00Mzk0LTkzODItMzMyMmQxNDg0
MTBiDQpMT0NBVElPTjpPcmxhbmRvIDogDQpTVU1NQVJZO0xBTkdVQUdFPWVuLXVzOkNvbXB1dGVy
IFJlZnJlc2ggQXBwb2ludG1lbnQNCkJFR0lOOlZBTEFSTQ0KVFJJR0dFUjotUFQxNDQwTQ0KQUNU
SU9OOkRJU1BMQVkNCkRFU0NSSVBUSU9OOlJlbWluZGVyDQpFTkQ6VkFMQVJNDQpFTkQ6VkVWRU5U
DQpFTkQ6VkNBTEVOREFSDQo=
----boundary_0_8278962b-71cf-4ca1-9a64-5fb9629f2042--

Upvotes: 3

Views: 3977

Answers (1)

Ishan Juneja
Ishan Juneja

Reputation: 49

Try using the below code which contains a method to attach inline images along with email invitations and normal attachments just adjust the code according to your need

private String senderAddress = "YOUR EMAIL ADDRESS";
    private final String password = "YOUR EMAIL PASSWORD";
    public static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmm'00'");
    public static SimpleDateFormat dateParser = new SimpleDateFormat("dd-MM-yyyy HH:mm");
    public static SimpleDateFormat dateFormater = new SimpleDateFormat("dd-MM-yyyy");

    public String getSenderAddress() {
        return senderAddress;
    }

    public String getPassword() {
        return password;
    }

    public Session getMailSession() {

        Properties props = new Properties();
        props.put("mail.smtp.host", "smtp.gmail.com");
        props.put("mail.smtp.port", "587");
        props.put("mail.smtp.auth", "true");
        props.put("mail.smtp.starttls.enable", "true");

        return Session.getInstance(props, new javax.mail.Authenticator() {
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(getSenderAddress(), getPassword());
            }
        });
    }

    public String newSend(Invitation invitation, String methodType)  {

        String UID = null;

        // register the text/calendar mime type
        MimetypesFileTypeMap mimetypes = (MimetypesFileTypeMap) MimetypesFileTypeMap.getDefaultFileTypeMap();
        mimetypes.addMimeTypes("text/calendar ics ICS");

        // register the handling of text/calendar mime type
        MailcapCommandMap mailcap = (MailcapCommandMap) MailcapCommandMap.getDefaultCommandMap();
        mailcap.addMailcap("text/calendar;; x-java-content-handler=com.sun.mail.handlers.text_plain");

        MimeMessage message = new MimeMessage(getMailSession());
        try {
            message.setFrom(new InternetAddress(senderAddress));
            message.setSubject(invitation.getSubject());
            message.addRecipient(Message.RecipientType.TO, new InternetAddress(invitation.getTo()));

            Multipart multipart = new MimeMultipart("mixed");
            invitation.setDescription(getHtmlWithEncodedImages(multipart,invitation));

            Multipart mpMixedAlternative = newChild(multipart, "alternative");

            // html part
            BodyPart messageBodyPart = buildHtmlTextPart(invitation);
            mpMixedAlternative.addBodyPart(messageBodyPart);

            // Add part two, the calendar
            BodyPart calendarPart = buildCalendarPart(invitation, methodType);
            calendarPart.setDisposition(BodyPart.INLINE);
            mpMixedAlternative.addBodyPart(calendarPart);

            addAttachment(multipart);
            // Put the multipart in message
            message.setContent(multipart);

            // send the message
            Transport transport = getMailSession().getTransport("smtp");
            transport.connect();


            transport.sendMessage(message, message.getAllRecipients());

            transport.close();
            UID = invitation.getTo() + "" + invitation.getStartdate().toString() + "" + invitation.getStarttime();
        } catch (Exception e) {
            //TODO logger log exception
        }
        return UID;
    }

    private void addAttachment(Multipart multipart) {

        MimeBodyPart mbpAttachment = new MimeBodyPart();

        File file=new File("C:/users/ishan.juneja/Desktop/temp.txt");
        DataSource datasource=new FileDataSource(file);
        try {
            mbpAttachment.setDataHandler(new DataHandler(datasource));
            mbpAttachment.setDisposition(BodyPart.ATTACHMENT);

            mbpAttachment.setFileName("temp.txt");
            multipart.addBodyPart(mbpAttachment);
        } catch (MessagingException e) {
            // TODO logger log exception
        }


    }

    public String uploadImage(MultipartFile file){

        byte[] bytes;
        try {
            String path = System.getProperty("catalina.home");
            File dir = new File(path+"/tmpFiles");
            if(!dir.isDirectory()){
                dir.mkdir();
                }
            bytes = file.getBytes();
            File serverfile = new File(dir.getAbsolutePath()+"/"+file.getName()+Math.random()+".png");
            BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(serverfile));
            stream.write(bytes);
            stream.close();

            System.out.println("file uploaded "+serverfile.getAbsolutePath());

            String hostname="Unknown";
            try {
                hostname = InetAddress.getLocalHost().getHostName();
            } catch (UnknownHostException e1) {
                e1.printStackTrace();
            }


            return "{\"location\" : \"http://"+hostname+":8080/emailinvitation/image/path/"+serverfile.getName()+"\"}";

        } catch (IOException e) {
        }

        return "{\"location\":\"http://localhost:8080/emailinvitation/image/path/unknown.png\"}";
    }

    private Multipart newChild(Multipart parent, String alternative) {
        MimeMultipart child = new MimeMultipart(alternative);
        MimeBodyPart mbp = new MimeBodyPart();
        try {
            parent.addBodyPart(mbp);
            mbp.setContent(child);
        } catch (MessagingException e) {
            // TODO logger log exception
        }

        return child;
    }

    private BodyPart buildCalendarPart(Invitation invitation, String methodType) throws Exception {

        Date date1 = dateParser.parse(dateFormater.format(invitation.getStartdate()) + " " + invitation.getStarttime());
        String startDate = timeZoneToUTC(invitation, date1);

        Date date2 = dateParser.parse(dateFormater.format(invitation.getEnddate()) + " " + invitation.getEndtime());
        String endDate = timeZoneToUTC(invitation, date2);

        BodyPart calendarPart = new MimeBodyPart();


        String calendarContent = "BEGIN:VCALENDAR\n" + "METHOD:" + methodType + "\n" + "PRODID: BCP - Meeting\n"
                + "VERSION:2.0\n" + "X-WR-TIMEZONE:" + invitation.getTimezone() + "\n" + "BEGIN:VEVENT\n" + "DTSTAMP:"
                + startDate + "Z\n" + "DTSTART:" + startDate + "Z\n" + "DTEND:" + endDate + "Z\n"
                + "SUMMARY:test request\n" + "UID:" + invitation.getTo() + "" + startDate + "\n"
                + "ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=FALSE:MAILTO:" + invitation.getTo() + "\n"
                + "ORGANIZER:MAILTO:" + senderAddress + "\n" + "LOCATION:Test\n"  + "SEQUENCE:0\n"
                + "PRIORITY:5\n" + "CLASS:PUBLIC\n" + "STATUS:CONFIRMED\n" + "TRANSP:OPAQUE\n" + "BEGIN:VALARM\n"
                + "ACTION:DISPLAY\n" + "DESCRIPTION:REMINDER\n" + "TRIGGER;RELATED=START:-PT00H15M00S\n"
                + "END:VALARM\n" + "END:VEVENT\n" + "END:VCALENDAR";

        calendarPart.addHeader("Content-Class", "urn:content-classes:calendarmessage");
        calendarPart.setContent(calendarContent, "text/calendar;method=" + methodType);

        return calendarPart;
    }

    private BodyPart buildHtmlTextPart(Invitation invitation) {

        MimeBodyPart descriptionPart = new MimeBodyPart();
        String content = invitation.getDescription();
        try {
            descriptionPart.setContent(content, "text/html; charset=utf-8");
        } catch (MessagingException e) {
            // TODO logger log exception

        }
        return descriptionPart;
    }

    private void addImages(Multipart parent,String filename)  {

        MimeBodyPart mbpAttachment = new MimeBodyPart();

        try {

            File file = new File("C:\\apache-tomcat-8.5.24\\tmpFiles\\"+filename);
            FileDataSource ds = new FileDataSource(file);
            mbpAttachment.setDataHandler(new DataHandler(ds));
            mbpAttachment.setDisposition(BodyPart.INLINE);
            mbpAttachment.setHeader("Content-ID", filename);
            mbpAttachment.setFileName(filename);
            parent.addBodyPart(mbpAttachment);
        } catch (Exception e) {
            //TODO logger log exception

        }


    }

    private String timeZoneToUTC(Invitation invitation, Date date) {
        ZoneId zone = ZoneId.of(invitation.getTimezone());

        String date1 = dateParser.format(date);

        DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm").withZone(zone);

        ZonedDateTime utc = ZonedDateTime.parse(date1, fmt).withZoneSameInstant(ZoneId.of("UTC"));

        Timestamp sqlTs = Timestamp.valueOf(utc.toLocalDateTime());

        return dateFormat.format(sqlTs);
    }

    public String getHtmlWithEncodedImages(Multipart parent,Invitation invitation) {

        String temp = invitation.getDescription();
        String html="<html><body>"+invitation.getDescription()+"</body></html>";
        Document document = Jsoup.parse(html);
        Elements allElements=document.body().getElementsByTag("img");
        for (Element element : allElements) {
            String elementstring=element.toString();
            String tempfilename = elementstring.substring(elementstring.indexOf("file"));
            String filename=tempfilename.substring(0,tempfilename.indexOf('"'));
            temp=temp.replace(elementstring.substring(elementstring.indexOf('"'), elementstring.lastIndexOf('"')),"\"cid:"+filename);

            addImages(parent, filename);
        }
        System.out.println("--++--"+temp);
        return temp;
    }

Upvotes: 1

Related Questions