Friday, February 6, 2015

PHP: send multiple events to MS Outlook 2010 calendar through one email

I was assigned to a project where I had to send multiple events through one email to MS Outlook 2010 calendar.
In a nutshell, below is the requirement
  • An ical object should be populated with multiple events & this should be sent with the first email. 
  • Once sent, these multiple events should be reflected in MS Outlook 2010 Calendar.
  • Later same events should be updated with the second/third/etc emails. Updated events may contain different summaries or canceled events.
I had a working solution for Google Calendar. So the first thing I did was, I have installed sendmail in my linux machine & tried to send the same mail to outlook using sendmail.
Below is the working solution with Google Calendar.
<?php 
$ical4 = 'BEGIN:VCALENDAR
PRODID:-//Microsoft Corporation//Outlook 10.0 MIMEDIR//EN
VERSION:2.0
CALSCALE:GREGORIAN
METHOD:REQUEST
BEGIN:VEVENT
DTSTART:20150310T183001Z
DTEND:20150310T182959Z
DTSTAMP:20150310T183000Z
UID:2015-03-10_leave_24@gmail.com
ORGANIZER:MAILTO:organizer@gmail.com
ATTENDEE:MAILTO:testuser1@gmail.com
DESCRIPTION:Test E1 Desc
STATUS:CONFIRMED
SEQUENCE:0
SUMMARY:Test E1
TRANSP:OPAQUE
END:VEVENT
BEGIN:VEVENT
DTSTART:20150311T183001Z
DTEND:20150311T182959Z
DTSTAMP:20150310T183000Z
UID:2015-03-11_leave_25@gmail.com
ORGANIZER:MAILTO:organizer@gmail.com
ATTENDEE:MAILTO:testuser1@gmail.com
DESCRIPTION:Test E2 Desc
STATUS:CONFIRMED
SEQUENCE:0
SUMMARY:Test E2
TRANSP:OPAQUE
END:VEVENT
END:VCALENDAR';


$from_name = "My Name";
    $from_address = "myname@mydomain.com";
    $subject = "Test Events_1";

//Create Mime Boundry
    $mime_boundary = "----Meeting Booking----".md5(time());

    //Create Email Headers
    $headers = "From: ".$from_name." <".$from_address.">\n";
    $headers .= "Reply-To: ".$from_name." <".$from_address.">\n";

    $headers .= "MIME-Version: 1.0\n";
    $headers .= "Content-Type: multipart/alternative; boundary=\"$mime_boundary\"\n";
    $headers .= "Content-class: urn:content-classes:calendarmessage\n";

    //Create Email Body (HTML)
    $message = "";
    $message .= "--$mime_boundary\n";
    $message .= "Content-Type: text/html; charset=UTF-8\n";
    $message .= "Content-Transfer-Encoding: 8bit\n\n";

    $message .= "<html>\n";
    $message .= "<body>\n";
    $message .= '<p>Dear user,</p>';
    $message .= '<p>Here is my HTML Email / Used for Meeting Description</p>';    
    $message .= "</body>\n";
    $message .= "</html>\n";
    $message .= "--$mime_boundary\n";

$message .= 'Content-Type: text/calendar; name="meeting.ics";method=REQUEST; charset=utf-8\n';
$message .= 'Content-Disposition: inline;\n';
    $message .= "Content-Transfer-Encoding: 2048bit\n\n";
    $message .= $ical4; 

    //SEND MAIL
    $mail_sent = @mail( $email, $subject, $message, $headers );

    if($mail_sent)     {
        return true;
    } else {
        return false;
    }  

When the email is sent to "testuser1@gmail.com", in testuser1's Google Calendar two events are published successfully. However if you save the $ical4 content as "meeting.ics" file & manually import it to MS Outlook 2010, multiple events will be reflected in outlook calendar too. But, if you send it through the email (using sendmail or SMTP), it will only add the first event to the MS Outlook 2010 calendar.
After researching for a long time I found that sending multiple events through one email as above, is not supported for MS Outlook. As a workaround, you could send a recurring event with multiple events with some MS Outlook specific parameters. 

Below is the working calendar content with MS Outlook 2010.
Scenario: 
  • I'm generating a leave request from 2015-01-15 to 2015-01-22. 
  • 2015-01-17 & 2015-01-18 are weekends.
  • 2015-01-19 is a company holiday.
  • So I do not want to reflect these 3 days in outlook calendar.
Below is the ics content for this scenario.

BEGIN:VCALENDAR
PRODID:-//Microsoft Corporation//Outlook 14.0 MIMEDIR//EN
VERSION:2.0
METHOD:REQUEST
X-MS-OLK-FORCEINSPECTOROPEN:TRUE

BEGIN:VEVENT
CLASS:PUBLIC
DESCRIPTION:Leave Event
DTEND;TZID="Unnamed Time Zone 1":20150115T113000Z
DTSTAMP:20150206T183000Z
DTSTART;TZID="Unnamed Time Zone 1":20150115T033000Z
PRIORITY:5
RRULE:FREQ=DAILY;COUNT=8
SEQUENCE:0
SUMMARY;LANGUAGE=en-us:Leave N/A
TRANSP:OPAQUE
UID:2015-01-15_leave_74@orangehrm.us.com
ORGANIZER:MAILTO:organizer@orangehrm.us.com
ATTENDEE:MAILTO:testuser1@gmail.com
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
END:VEVENT

BEGIN:VEVENT
CLASS:PUBLIC
DTEND:20150115T113000Z
DTSTAMP:20150206T183000Z
DTSTART:20150115T033000Z
RECURRENCE-ID:20150115T033000Z
SEQUENCE:0
SUMMARY:Vacation Leave - Pending approval - emp_1 emp_1 emp_1
TRANSP:OPAQUE
UID:2015-01-15_leave_74@orangehrm.us.com
ORGANIZER:MAILTO:organizer@orangehrm.us.com
ATTENDEE:MAILTO:testuser1@gmail.com
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
END:VEVENT

BEGIN:VEVENT
CLASS:PUBLIC
DTEND:20150116T113000Z
DTSTAMP:20150206T183000Z
DTSTART:20150116T033000Z
RECURRENCE-ID:20150116T033000Z
SEQUENCE:0
SUMMARY:Vacation Leave - Pending approval - emp_1 emp_1 emp_1
TRANSP:OPAQUE
UID:2015-01-15_leave_74@orangehrm.us.com
ORGANIZER:MAILTO:organizer@orangehrm.us.com
ATTENDEE:MAILTO:testuser1@gmail.com
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
END:VEVENT

BEGIN:VEVENT
CLASS:PUBLIC
DTEND:20150120T113000Z
DTSTAMP:20150206T183000Z
DTSTART:20150120T033000Z
RECURRENCE-ID:20150120T033000Z
SEQUENCE:0
SUMMARY:Vacation Leave - Pending approval - emp_1 emp_1 emp_1
TRANSP:OPAQUE
UID:2015-01-15_leave_74@orangehrm.us.com
ORGANIZER:MAILTO:organizer@orangehrm.us.com
ATTENDEE:MAILTO:testuser1@gmail.com
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
END:VEVENT

BEGIN:VEVENT
CLASS:PUBLIC
DTEND:20150121T113000Z
DTSTAMP:20150206T183000Z
DTSTART:20150121T033000Z
RECURRENCE-ID:20150121T033000Z
SEQUENCE:0
SUMMARY:Vacation Leave - Pending approval - emp_1 emp_1 emp_1
TRANSP:OPAQUE
UID:2015-01-15_leave_74@orangehrm.us.com
ORGANIZER:MAILTO:organizer@orangehrm.us.com
ATTENDEE:MAILTO:testuser1@gmail.com
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
END:VEVENT

BEGIN:VEVENT
CLASS:PUBLIC
DTEND:20150122T113000Z
DTSTAMP:20150206T183000Z
DTSTART:20150122T033000Z
RECURRENCE-ID:20150122T033000Z
SEQUENCE:0
SUMMARY:Vacation Leave - Pending approval - emp_1 emp_1 emp_1
TRANSP:OPAQUE
UID:2015-01-15_leave_74@orangehrm.us.com
ORGANIZER:MAILTO:organizer@orangehrm.us.com
ATTENDEE:MAILTO:testuser1@gmail.com
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
END:VEVENT

END:VCALENDAR

Explanation:
  • 1st inner event is the default event. 
  • This should include the RRULE specifying the recurring frequency & count.
  • In my leave request I need event to recur from 2015-01-15 to 2015-01-22 (8 days) daily.
  • This should contain the same DTSTART, DTEND, DTSTAMP values of the 1st day event.
  • All events including the default event should contain the same UID & SEQUENCE.
  • Other than the default event, all other events should contain unique RECURRENCE-IDs.
  • Since I do not want events for 17th, 18th & 19th dates, events are not specified for these 3 days.
  • I have given the default event's SUMMARY as "Leave N/A", so that the missing events will have this summary when displaying inactive events in the calendar.
Below is how this recurring event reflects in the outlook calendar.
 
Monthly view
 
 
Events from 18th to 22nd

Opened 2015-01-20 event




Then the next thing I wanted was to update the same events through the next email. In order to update, you need to change the sequence of all events to a higher number while keeping recurrence ids & uids the same as previous content. If you need to cancel an inner event you can simply remove that event portion from the calendar content.

Scenario:
  • I'm changing leave request as below.
    • 2015-01-20 - Schedule
    • 2015-01-21 - Cancel (Remove from calendar)
    • 2015-01-22 - Reject (Remove from calendar)
  • All other dates are remained same.

Below is the ics content for this scenario.
BEGIN:VCALENDAR
PRODID:-//Microsoft Corporation//Outlook 14.0 MIMEDIR//EN
VERSION:2.0
METHOD:REQUEST
X-MS-OLK-FORCEINSPECTOROPEN:TRUE

BEGIN:VEVENT
CLASS:PUBLIC
DESCRIPTION:Leave Event
DTEND;TZID="Unnamed Time Zone 1":20150115T113000Z
DTSTAMP:20150206T183000Z
DTSTART;TZID="Unnamed Time Zone 1":20150115T033000Z
PRIORITY:5
RRULE:FREQ=DAILY;COUNT=8
SEQUENCE:2
SUMMARY;LANGUAGE=en-us:Leave N/A
TRANSP:OPAQUE
UID:2015-01-15_leave_74@orangehrm.us.com
ORGANIZER:MAILTO:organizer@orangehrm.us.com
ATTENDEE:MAILTO:testuser1@gmail.com
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
END:VEVENT

BEGIN:VEVENT
CLASS:PUBLIC
DTEND:20150115T113000Z
DTSTAMP:20150206T183000Z
DTSTART:20150115T033000Z
RECURRENCE-ID:20150115T033000Z
SEQUENCE:2
SUMMARY:Vacation Leave - Pending approval
TRANSP:OPAQUE
UID:2015-01-15_leave_74@orangehrm.us.com
ORGANIZER:MAILTO:organizer@orangehrm.us.com
ATTENDEE:MAILTO:testuser1@gmail.com
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
END:VEVENT

BEGIN:VEVENT
CLASS:PUBLIC
DTEND:20150116T113000Z
DTSTAMP:20150206T183000Z
DTSTART:20150116T033000Z
RECURRENCE-ID:20150116T033000Z
SEQUENCE:2
SUMMARY:Vacation Leave - Pending approval
TRANSP:OPAQUE
UID:2015-01-15_leave_74@orangehrm.us.com
ORGANIZER:MAILTO:organizer@orangehrm.us.com
ATTENDEE:MAILTO:testuser1@gmail.com
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
END:VEVENT

BEGIN:VEVENT
CLASS:PUBLIC
DTEND:20150120T113000Z
DTSTAMP:20150206T183000Z
DTSTART:20150120T033000Z
RECURRENCE-ID:20150120T033000Z
SEQUENCE:2
SUMMARY:Vacation Leave - Scheduled
TRANSP:OPAQUE
UID:2015-01-15_leave_74@orangehrm.us.com
ORGANIZER:MAILTO:organizer@orangehrm.us.com
ATTENDEE:MAILTO:testuser1@gmail.com
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
END:VEVENT

END:VCALENDAR
 
Below is how this recurring event reflects in the outlook calendar.
Updated recurring event - Monthly view

Updated recurring event - 15th to 22nd

Event details for 2015-01-20

Limitations with recurring events for MS Outlook
  • Once calendar contents are changed to support outlook, it will no longer support Google Calendar.
  • Events are not published to the calendar unless outlook user clicks & opens the relevant email.
  • Once user opens the email, email subject will be changed to whatever the description you have given for the default initial event.
  • If there are removed events, within the recurring event, those days will be displayed as inactive events with the same details given in default event.
    • Eg.Send a recurring event from 2015-01-01 to 2015-01-06. Default event's summary is given as Leave N/A
      • 2015-01-01 - Duration 10am-11am (Summary: Pending Approval)
      • 2015-01-02 - No event
      • 2015-01-03 - No event
      • 2015-01-04 - No event
      • 2015-01-05 - Duration Full day (Summary: Pending Approval)
      • 2015-01-06 - Duration Full day (Summary: Pending Approval)
    • Email is sent with details. Event is created as below.
      • 2015-01-01 - 10am-11am active event (Summary: Pending Approval)
      • 2015-01-02 - inactive event 10am-11am (Summary: Leave N/A)
      • 2015-01-03 - inactive event 10am-11am (Summary: Leave N/A)
      • 2015-01-04 - inactive event 10am-11am (Summary: Leave N/A)
      • 2015-01-05 - Full Day active event (Summary: Pending Approval)
      • 2015-01-06 - Full Day active event (Summary: Pending Approval)
Special thank goes to my colleagues Lahiru Nirmal who pointed out some specific required parameters which are needed for MS Outlook calender & Nirmal Wijesinghe who supported me in the research.

Hope this will be helpful for others as I couldn't find any working solution for above scenario anywhere else in the Internet.

No comments:

Post a Comment