Wednesday, January 25, 2017

How to send Outlook Meeting invite from SharePoint calendar

I like to blog on difficult issues that i face while developing.

I have built a calendar management system in SharePoint on top of SharePoint calendar.
One of the functionalities it offers to users is to add the event to outlook as a meeting. I had built 'add to outlook' i.e. outlook meeting invite functionality using downloading event in ICS file and it works fine but users have to click many times since ICS file would download, then open in outlook, save in outlook, etc. This time, I wanted to offer more efficient solution so users don't have to click multiple times.

So i came up with sending event invitation to users as an outlook meeting invite. No Clicks!
Below is how i did it:

Problem-Scenario:
Programmatically send Outlook meeting invite from SharePoint

Solution:
Here, I will cover only the part where you send Outlook meeting invite programmatically.

//Read comments for details:
//Replace Constants.variable name with your variable value.
//Replace event item column values with your item values.

public void SendMeetingInviteEmail(SPListItem currentItem)
        {
            string currentUserEmailAddress = SPContext.Current.Web.CurrentUser.Email;
            string currentUserDisplayName = SPContext.Current.Web.CurrentUser.Name;

            //get all event columns and store them in variables. Easy step, so removed code for it.
            //......//

            //Create Mail Message object with required details

            MailMessage msg = new MailMessage();
           
            msg.From = new MailAddress(Constants.EventsAdminEmailAddress,                       Constants.EventsAdminDisplayName);
            msg.To.Add(new MailAddress("steve@apple.com", "steve jobs"));
           
           // CC is optional 
            msg.CC.Add(new MailAddress("amigo@spanish.com", "Amigo Friend"));

            msg.Subject = eventTitle; //from event item
            msg.Body = eventPresenter + eventDescriptionAsText; //from event item
            msg.Headers.Add("Content-class", "urn:content-classes:calendarmessage");

/* Most important part is to create alternate views:
    You need to create two alternate views. One for calendar (mandatory) and another one for the mail      body. (optional)

     I highly recommend creating both views even if second one is optional. 
     If you don't create a       separate alternate view for mail body, it will be simple text with no    HTML whatsoever. So it will be a straight line. Users are not going to like that.
     Mail body alternate view will allow the body to be sent as html.
*/
            //Calendar Alternate View (mandatory)
            StringBuilder str = new StringBuilder();
            str.AppendLine("BEGIN:VCALENDAR");
            str.AppendLine("PRODID:-//Schedule a Meeting");//no need to change to SharePoint ID
            str.AppendLine("VERSION:2.0");
            str.AppendLine("METHOD:REQUEST");
            str.AppendLine("BEGIN:VEVENT");
            str.AppendLine("CLASS:PUBLIC");

/*All date time objects should be in UTC format else the meeting invite will break. 
   Most probably this is an Outlook meeting requirement

   eventStartTime and eventEndTime objects are converted into UTC format.
   You may ask how to do it, so here is an example:

   eventStartTime =               DateTimeOffset.Parse(currentItem[Constants.EventsListFieldStartTime].ToString()).UtcDateTime;
          
*/
            str.AppendLine(string.Format("CREATED:{0:yyyyMMddTHHmmss}", DateTime.Now));
            str.AppendLine(string.Format("DTSTART:{0:yyyyMMddTHHmmssZ}", eventStartTime));
            str.AppendLine(string.Format("DTSTAMP:{0:yyyyMMddTHHmmssZ}", DateTime.UtcNow));
            str.AppendLine(string.Format("DTEND:{0:yyyyMMddTHHmmssZ}", eventEndTime));
            str.AppendLine(string.Format("LOCATION:{0}", eventLocation));
            str.AppendLine(string.Format("UID:{0}", Guid.NewGuid().ToString()));
            str.AppendLine(string.Format("DESCRIPTION:{0}", msg.Body));
            str.AppendLine(string.Format("X-ALT-DESC;FMTTYPE=text/html:{0}", msg.Body));
            str.AppendLine(string.Format("SUMMARY;LANGUAGE=en-us: Scheduled for {0}", msg.Subject));
            str.AppendLine(string.Format("ORGANIZER:MAILTO:{0}", msg.From.Address));

         
/*
Below is how we create meeting alert for user.
              TRIGGER:-PT15M
It means that user alert will activate 15 minutes before the meeting starting time. 
You can change it based on your requirements   
*/
            str.AppendLine("BEGIN:VALARM");
            str.AppendLine("TRIGGER:-PT15M");
            str.AppendLine("ACTION:DISPLAY");
            str.AppendLine("DESCRIPTION:Reminder");
            str.AppendLine("END:VALARM");
            str.AppendLine("END:VEVENT");
            str.AppendLine("END:VCALENDAR");

            string eventsHomeUrl = SPContext.Current.Site.Url + Constants.EventsWebUrl;

            SmtpClient smtpclient = new SmtpClient(GetSMTPHostName(eventsHomeUrl));
            smtpclient.Credentials = CredentialCache.DefaultNetworkCredentials;

//add mail body HTML type alternate view
            ContentType HtmlCtype = new ContentType("text/html");

//eventPresenter and eventDescription are event item column values
            string bodyHtml = GetBodyHtml(eventPresenter + "<br>" + eventDescription);

            AlternateView HtmlView = AlternateView.CreateAlternateViewFromString(bodyHtml, HtmlCtype);
            msg.AlternateViews.Add(HtmlView);
           

//REMEMBER to add Calendar view last / all other views before it else Outlook invite won't work
            ContentType contype = new ContentType("text/calendar");
            contype.Parameters.Add("method", "REQUEST");

            AlternateView avCal = AlternateView.CreateAlternateViewFromString(str.ToString(), contype);
            msg.AlternateViews.Add(avCal);
            smtpclient.Send(msg);
        }

/* 
Get SMTP host name from SharePoint outgoing e-mail settings. Good practice to set it at web application level.
*/

private static string GetSMTPHostName(string siteUrl)
        {
            using (SPSite site = new SPSite(siteUrl))
            {
                //Get the SMTP host name from “Outgoing e-mail settings”
                return site.WebApplication.OutboundMailServiceInstance.Parent.Name;
            }
        }

/*
Last but not least is how to create mail body. It is self explanatory. See below:
*/

private static string GetBodyHtml(string body)
        {
             string bodyHtml =
                 @"<!DOCTYPE HTML PUBLIC ""-//W3C//DTD HTML 3.2//EN"">"
                 + @"<HTML>"
                 + @"<HEAD>"
                 + @"<META HTTP-EQUIV=""Content-Type"" CONTENT=""text/html; charset=utf-8"">"
                 + @"<META NAME=""Generator"" CONTENT=""MS Exchange Server version 6.5.7652.24"">"
                 //+ @"<TITLE>{0}</TITLE>"
                 + @"</HEAD>"
                 + @"<BODY>"
                 + @"<BR>"
                 + @"{0}"
                 + @"</BODY>"
                 + @"</HTML>";

             return String.Format(bodyHtml, body);
        }

Do comment if this blog has helped you!