logo

NJP

ServiceNow Calendar Invites

Import · Sep 16, 2016 · article

This is a tutorial on how to set up Calendar Invites to be sent to Outlook via ServiceNow, along with potentially how to capture any responses the invitees send back.

Update Feb 12, 2020: Official ServiceNow documentation for reference: https://docs.servicenow.com/bundle/newyork-servicenow-platform/page/administer/notification/referenc...

What we can do with this feature

  • Send new calendar invites from ServiceNow to email clients
  • Update existing calendar invites created by ServiceNow
  • Delete existing calendar invites
  • Update ServiceNow when an invite is accepted/tentatived/declined*
  • Update ServiceNow when an invite has notes added to the body*

  • Instance running at least Calgary, maybe even Berlin

  • Subproduction instance set to email a specific email address is highly recommended

  • Outlook, though other email clients can work*

  • A table that contains two fields that are in the date/time format

  • Lots of patience

  • A little knowledge on how iCalendar works

  • Creating notifications

  • Creating email templates

  • Creating business rules OPTIONAL

  • Creating events OPTIONAL

  • Basic Javascript

  • How to Google

  • Reoccurring calendar invites are incredibly complicated (might dive into this later)

  • Mail scripting is unavailable when sending calendar invites

  • Can't account for all mail clients or the human element when transforming responses sent back to ServiceNow

  • Different mail servers interact with meetings in slightly different ways (Exchange 07/10/13, O365, Gmail)

  • HTML is possible to use....sort of. We'll touch on this a little

Together we'll walk through how this process works and a lot of the nuances that come up along the way. This by no means 100% covers all aspects of how to utilize this function of ServiceNow, but hopefully touches on a bit more than just the scattered Wiki articles and Google search results. I'm positive that there will be some better or more creative solutions out there on how to handle certain sections of this guide, and I hope that the community is positive and willing to share such information!

First we need to identify which table is going to house the information that will be in our meeting invites. The most critical pieces of information that have to be in this table are the start date/time and end date/time. BOTH THESE PIECES OF INFORMATION HAVE TO COME FROM A DATE/TIME FIELD. Everything else, who it gets sent to, the subject, the location, the body, can be fanangled some other way, but when the meeting should start and end have to be fields in a table.

Next, we need a way to trigger the system to send out these calendar invites. This can be done one of two ways

  1. Conditions that are built into a notification
  2. Event that triggers a notification, either through a business rule or workflow

This is personal preference, and depends on your own requirements of when calendar invites should be sent.

Event Based Notifications

If you choose to use events, I recommend creating three new events in the event registry (System Policy -> Events -> Registry)

  1. One event for new calendar invites
  2. One event for updating existing calendar invites
  3. One event for deleting existing calendar invites

Please consult this Wiki if you are not familiar with creating new events

Now that we have built our events, we need to create a trigger to fire them off.

We can either do this with a business rule, or with a workflow. It might be possible to do it based off a client script that calls a script include, but I won't get into that in this article.

For a business rule, we need to set up some simple IF statements to tell the system when to fire the event and what the event will contain for parameters. The previous Wiki article contains several examples on how this works, and I highly recommend taking a look at it.

if (current.operation() == 'insert') {
  gs.eventQueue("change.inserted", current, gs.getUserID(), gs.getUserName());
}

This is even easier with a workflow, as there is an event trigger that you can drag in and tell the system which event to fire and what the parameters should be at any point of the workflow.

Let's build up a new notification that will handle sending out the calendar invite. You might need more than one to handle inserting, updating, and deleting calendar invites, but we'll start with just one.

While you have a blank notification in front of you, you might need to personalize the form to show the "Type" field. We need to change this field from "EMAIL" to "Meeting Invitation".

  1. Select the table that we defined earlier and has our two date/time fields in it.
  2. We need to set when the notification will fire. If you chose to go the event route, select your event here. If you are using conditions, now is the time to define them.
  3. Set the "Content type" field on the "What it will contain" tab to "Plain text only". That's right, we can't do any mail scripting here. It's very annoying and greatly reduces the flexibility of the notification, but as of Eureka (and probably Fuji), we have to send out a plain text notification to make this work.

Please consult this Wiki if you are not familiar with creating notifications

http://wiki.servicenow.com/index.php?title=Email%5FNotifications

That's it for now. Let's save this notification as we will come back to it later for a few more things.

The Import Export Maps Table

Our next step is to have ServiceNow translate those two date/time fields I keep on mentioning into a format that iCalendar recognizes. The quickest way to get there is to type sys_impex_map.list into your Type Filter Text on the left side, or search for that in the list of tables.

We only need to create one new entry here. Click the new button. For the name of the new record, it has to be icalendar.your_table_name. Typing in something else or misspelling the table name will result in the start and end times for the calendar event to not work correctly.

Select the table in question as well, and make sure that the "Type" field is "icalendar".

You can give the record a description as well if you want, but otherwise we can save the record.

Some related lists will appear. We are interested in the "Field Maps" related list.

Click "New", and you will be greeted by a wizard. Select "Mapping to a database field" to continue.

Here we can see that the "Map" and "Table" fields are already filled out. There are only two that we need to fill in.

For "External Name", we now start to get into the iCalendar language.

  • dtstart - this denotes when a calendar invite will start REQUIRED
  • dtend - this denotes when a calendar invite will end REQUIRED
  • description - if you have a multi line description, we'll need to enter this in so that line breaks are handled correctly OPTIONAL
  • alarm_time - if you want to have a flexible time for an alarm to trigger, this is needed for the date/time to translate correctly OPTIONAL

Enter in one of those names into the "External Name" field. Again, dtstart and dtend are required for this to work.

Finally select the field under "Database Field" that houses the information in question. So dtstart would go to the field you have denoted as when the meeting should begin, dtend to the end of the meeting, etc.

Click update and we're on our way. Continue the process until you at lease have dtstart and dtend mapped, and if it's relevant, description and/or alarm_time.

More useful information can be found in this wiki article.

We now need to create a notification template that will house the iCalendar code which is used by Outlook to create the meeting.

Go to System Policy -> Email -> Templates, and click New.

Name it whatever you'd like, and select the same table that we keep on using.

IMPORTANT NOTE: Prior to Eureka, everything we type will go into the Message box. Eureka and after, it will either go into the Message box, or the Message text box if you've switched to the Rich HTML Editor. DO NOT POST THE ICALENDAR SCRIPT INTO THE Message HTML BOX AS YOU WILL RECEIVE AN INVALID .ICS FILE!!!

These websites contain very detailed breakdowns of all the different faces of how iCalendar works. It's very technical, but thorough and contains many examples.

http://www.kanzaki.com/docs/ical/

https://tools.ietf.org/html/rfc2446

There are a lot of lines we can add/remove or different lines we can change. We're going to stick with the basics plus a few useful extras in this article though.

We can reference fields from the table here and dot walk by using the ${field_name} notation here. Please remember though that any form of scripting is not allowed. For example, doing a ${URI_REF} will not produce a beautiful html hyperlink to your record, but instead will show a "https://blah blah blah"record number instead. Outlook 2013 is smart enough to identify a hyperlink and automatically make it selectable, but something like OWA is not, and will just show the link in plain text.

Use this for inserting new calendar events or updating existing one.

BEGIN:VCALENDAR
PRODID:-//Service-now.com//Outlook 11.0 MIMEDIR//EN
VERSION:2.0
METHOD:REQUEST
BEGIN:VEVENT
ATTENDEE;ROLE=REQ-PARTICIPANT;RSVP=TRUE:MAILTO:${to}
ORGANIZER:MAILTO:from email address
LOCATION:meeting location
DTSTART:${dtstart}
DTEND:${dtend}
UID:${sys_id}
DTSTAMP:${dtstamp}
DESCRIPTION:meeting body
SUMMARY:subject line
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
X-MICROSOFT-DISALLOW-COUNTER:TRUE
X-SNSYSID:${sys_id}
BEGIN:VALARM
TRIGGER:-PT15M
ACTION:DISPLAY
DESCRIPTION:Reminder
END:VALARM
END:VEVENT
END:VCALENDAR

ATTENDEE;ROLE=REQ-PARTICIPANT;RSVP=TRUE:MAILTO: - Who the invite will go to. Either specify a field here, or use ${to} if this is defined in the notificationORGANIZER:MAILTO: - Who is listed as the organizer. Recommend inserting the email for your instance if you want to be able to process attendee responses. NOTE: If you want this to be the email address for your ServiceNow instance, we can't use a system property here, so the easiest way is to hardcode it. Just remember that when testing.LOCATION: - Where the meeting will be held

DTSTART:${dtstart} - Our field from the sys_impex_map table. DO NOT CHANGE

DTEND:${dtend} - Our field from the sys_impex_map table. DO NOT CHANGE

UID:${sys_id} - Unique identifier of the calendar invite. Recommend using the sys_id of the record you are sending this invite from. NOTE: If you want to process responses, I recommend putting 'your_table_name${sys_id}' as it will save us some time later.DTSTAMP:${dtstamp} - Don't 100% remember what this is for, but let's just leave it be.

DESCRIPTION: - What shows up in the body of the invite. Can omit if defining the body in the notification

SUMMARY: - The subject line. Can omit if defining the subject in the notificationX-MICROSOFT-CDO-BUSYSTATUS:BUSY - Defines if the attendee is busy/free/etc. X-MICROSOFT-DISALLOW-COUNTER:TRUE - Prevents the attendees from proposing new times.

X-SNSYSID:{$sys_id} - Some other unique identifier. Again just use the sys_id

BEGIN:VALARM - Sets up an alarm if wanted. Can omit the section between here and END:VALARM if no alarm is desired

TRIGGER:-PT15M - How soon before the meeting will the alarm pop up. In this example, it is for 15 minutes

NOTE FOR GMAIL USERS: Just recently discovered that in it's current state (as of Oct 24th 2016), gmail does not accept the VALARM parameter. Please delete out anything involving the VALARM if you plan on sending the invites to a gmail client.

Use this for canceling old calendar events

BEGIN:VCALENDAR
PRODID:-//Service-now.com//Outlook 11.0 MIMEDIR//EN
VERSION:2.0
METHOD:CANCEL
BEGIN:VEVENT
STATUS:CANCELLED
ATTENDEE;ROLE=REQ-PARTICIPANT;RSVP=FALSE:MAILTO: ${to}
DTSTART:${dtstart}
DTEND:${dtend}
UID:${sys_id}
DTSTAMP:${dtstamp}
DESCRIPTION:
END:VEVENT
END:VCALENDAR

METHOD:CANCEL - Note that the method has changed to CANCEL

STATUS:CANCELLED - Note that we have added this new STATUS line

The template for inserting or updating a meeting invitation can be the same template if you wish. If you'd like to communicate to the attendees that there has been an update to the calendar invite, then you can do that with either separate templates or with separate notifications. Up to you.

The cancellation code has to live in its own template since it is so different.

Now that our template is complete, let's head back to the notification we built earlier so we can add in the template and do some finishing touches.

Open up your notification, and reference the email template that you just built in the "What it will contain" tab. You can use the "Who will receive" section without any issues to define who will be sent the calendar invite without issue.

If you'd like different calendar invites to be sent out for a new invite vs an update, make sure to at the least create another notification if not another template as well depending on what you'd like to be different. Also if you'd like to be able to delete invites, you will need another template and notification for that.

Save the notification, and now we're ready for testing!

If all goes well, you will receive a shiny new meeting invite in your Outlook with the dates, times, subject, location, etc. that you specified earlier. You can also look at the email logs to check the recipient to see if it was addressed correctly, even if your emails are set to go to a specific email address.

Check down below if things aren't quite going right.

ADVANCED: Deleting the record triggers deleting the calendar event!!!

If you'd like to have deleting the record you've been using be the trigger for also deleting the calendar event instead of some other method that preserves the record, then read on.

Make sure to create a deletion event, as I don't believe a condition on a notification can capture this.

Add this line to your business rule, or modify your current one for deleting.

if (current.operation() == 'delete') {
  gs.eventQueue("your deleted event name", null, Parm1: who this is sent to, Parm2: current.sys_id);
}

Check off "Event parm 1 contains recipient"

BEGIN:VCALENDAR
PRODID:-//Service-now.com//Outlook 11.0 MIMEDIR//EN
VERSION:2.0
METHOD:CANCEL
BEGIN:VEVENT
STATUS:CANCELLED
ATTENDEE;ROLE=REQ-PARTICIPANT;RSVP=FALSE:MAILTO: ${to}
DTSTART:${dtstart}
DTEND:${dtend}
UID:${event.parm2}
DTSTAMP:${dtstamp}
DESCRIPTION:
END:VEVENT
END:VCALENDAR

Change the UID line to "UID:${event.parm2}". We need to do this because the notification won't be able to reference the record anymore as it's been deleted. This is how we provide the unique identifier so the invite going out can still match with the existing meeting invite that lives on a calendar.

If you added in your table name to the UID, the it'd be "UID:table_name${event.parm2}"

That should handle deleting calendar invites correctly. Please note that the attendee will have the old meeting disappear and it will show up as a 30 minute block in their calendar with a message along the lines of "This meeting has been deleted. Click to remove from calendar".

HTML in Meeting Invites from ServiceNow....Sort of

A big drawback of the iCalendar file type is that HTML is not supported in the description field when building up a new meeting. So how is it then that when making a normal meeting in Outlook/Gmail you can throw in some lovely HTML bits? The answer is another tag in the iCalendar type called "X-ALT-DESC". Before you get your hopes up though, this isn't very straightforward. X-ALT-DESC uses some XML schemas from schemas-microsoft-com to build up how the event looks. If you'd like to see what this looks like, I'd recommend making a meeting for yourself in your mail client, exporting it out, and taking a look at X-ALT-DESC. The section contains all the good bits in it.</p> <p>Something I have not tested, but might work best would be to get a calendar invite going out of ServiceNow into your own client, edit meeting in your client to pretty it up, then see if applying the X-ALT-DESC that appears after editing it into the iCalendar template in ServiceNow does the trick for keeping all of the styling in the future.</p> <p>My calendar invite isn&#39;t sending.</p> <p>Make sure that any conditions you set for the notification to send are being met, or that you can find the event in the event log if you are using events. It&#39;s generally easier to start with simple parameters and build them up from there than to start complex and work backwards.</p> <p>I&#39;m getting an email with an attachment and a message that says &quot;This is not a valid .ics file&quot;.</p> <p>Lots of places to check here. First is to make sure that the notification Type is a Meeting Invitation, that the Content type is Plain text only, and that you are using an Email Template to house the iCalendar code.</p> <p>From there, check to make sure that your iCalendar code is placed in the Message Box or Message Text Box in the template, and not the Message HTML Box if you are using Eureka or higher versions of ServiceNow</p> <p>Finally check to make sure that you have created and mapped your fields in the sys_impex_map table.</p> <p>I get a meeting invite, but the start time is when the message was sent, and the end time is half an hour later.</p> <p>Head on over to the sys_impex_map table and make sure that your entry their is titled icalendar.table_name. Again, if it is not in that format, it will not work correctly.</p> <p>Also ensure that your dtstart and dtend fields in the sys_impex_map table are being mapped to date/time fields. Date fields will not work, nor will strings with dates and times in them.</p> <p>We&#39;re using a rather basic calendar invite here, but if you&#39;ve sent out meeting invite in Outlook or any other mail client then you know there is the possibility for so much more. Reading through those websites for the iCalendar code will provide a lot of information. Another great trick is to create a meeting similar to one you&#39;d want ServiceNow to send, saving it as an .ics file to your computer, then opening it in notepad++ or another editro to see what the code looks like for yourself.</p> <p>I briefly mentioned that reoccurring events are incredibly hard to do earlier. They are not impossible, but a large quantity of new fields beyond the regular start and end dates are required. Things like defining time zones, day of the week codes, and several other items are required. They also need to be in a very peculiar format.</p> <p><strong>Taking calendar invite responses and updating a record in ServiceNow with it.</strong></p> <p>Author&#39;s Note (September 23rd, 2016): It is important to preface this section with a little note. I developed this solution while using Outlook 2016 and OWA. After moving to Office 365 however, the method described below doesn&#39;t function anymore, as the actual iCalendar code is attached to the inbound email instead of showing up in the body. As always, I strongly urge testing to see if this solution will work for you. If anyone has any suggestions on somehow including the iCalendar code in the body again (or getting ServiceNow to read the .ics attachment!), I&#39;m all ears.</p> <p>As I never got to the bottom of the contents of a meeting invite coming into ServiceNow in either the sys_email body or an .ics file being Mail Server / Mail Client based, I&#39;d recommend writing some inbound logic to account for either just to cover your bases.</p> <p>If you have a need to know if an attendee or attendees responded to the invite, or if you&#39;d like to know if they typed in any additional comments when they responded to the invite, then read on.</p> <p>We will use an inbound email action to read the response, and then update some fields with the information that the inbound email action is able to glean.</p> <ul> <li>ORGANIZER:MAILTO in your iCalendar code is set to the email address for your instance</li> <li>You have some knowledge as to how inbound email actions work and are able to script them</li> <li>There is some place in the record that sends the calendar invite to insert the information we get back</li> </ul> <p><strong>NOTE:</strong> This was built on the assumption that the attendee is using Outlook (not O365). Other mail clients may return other information in the body of the email, so some trial and effort will be required to adjust your code to adapt for it.</p> <p>The first thing to do is decide what kind of information are you interested in from the attendee&#39;s response, and where will it go. If you need to make new fields for this information then do so now, or you can always put this information in a journal field. Individual fields work well if there is only one attendee, whereas a journal field might work best if there are multiple attendees since it can capture who has responded and when over time.</p> <p>Next is to head over to System Policy -&gt; Email -&gt; Inbound Actions and create a new Inbound Email Action</p> <p>Once again, we will target the same table that we have kept on referencing over and over again, as it is the table that houses the record that initially triggered the calendar invite.</p> <p>Type will be new, though if you want to capture if one of the attendees sent the invite to another person, I believe the type would then become a Forward.</p> <p>The inbound email action has to do two things here:</p> <ol> <li>Figure out if an email coming into the system is a response to our calendar invite</li> <li>Break down the content of the email to just give us the useful bits</li> </ol> <p>The first part can be a bit tricky, but I have a crafty solution.</p> <p>Remember earlier in our iCalendar code how there is a line for the calendar invites UID, aka it&#39;s unique number? Normally you&#39;re fine with just putting ${sys_id}, but if we put the name of the table we&#39;re using before the sys_id so it looks like UID:your_table_name${sys_id}, this gives us an easy way to track down the emails we want.</p> <p>From there, we can write a condition for the Inbound Email Action like looks something like - (email.body_text.indexOf(&#39;UID:table_name&#39;) &gt; -1)</p> <p>What this is telling the script to do is only process emails where it can see that UID:table_name piece in it.</p> <p><a href="https://sites.google.com/site/bradfordshelley/home/servicenow/Image6.png?attredirects=0" target="_blank" rel="noopener"><img src="https://sites.google.com/site/bradfordshelley/home/servicenow/Image6.png" alt=""></a></p> <p>Now to get the bits of the email we care about, since otherwise it will just spit back everything including the iCalendar code at us if we don&#39;t trim it down.</p> <p>The best way to do this is by doing an indexOf for the UID:table_name. With that information we can do a slice and find the sys_id of the record we want to update. UID on its own might not be enough, in case someone typed a word with &quot;uid&quot; in it.</p> <p>From there, run a GlideAggregate on the table we continuously use, and pull it up.</p> <p>Meeting responses will appear in the subject line, so a simple IF statement with an indexOf for Accepted, Tentative, or Declined will find that information out for you.</p> <p>Getting any additional notes that were typed in requires a bit more slicing. Do an indexOf &quot;BEGIN:VCALENDAR&quot;, and this will return where the iCalendar code begins. We want the information before that, so doing a slice between 0 and where BEGIN:VCALENDAR is will get us that information.</p> <p>From there, write a little javascript to handle capturing the information you care about, then updating it to your record with a .update();</p> <pre><code>var sbj = email.subject; //email subject var body = email.body_text; //email body var sysbody = body.indexOf(&#39;UID:table_name&#39;); var sysid = body.slice(sysbody +12, sysbody + 44); //get the custom identifier of the outlook event //gs.log(sysid + &#39;sys id of table_name record&#39;); //See if the sys_id from the subject line returns any records var ga = new GlideAggregate(&#39;table_name&#39;); ga.addAggregate(&#39;COUNT&#39;); ga.addQuery(&#39;sys_id&#39;,sysid);ga.query(); var answer = 0; if (ga.next()) { answer = gr.getAggregate(&#39;COUNT&#39;); //If there are no records, create a log if (answer == 0) { gs.log(&quot;No meeting response &quot; + sbj); //Record has been found. Update the invite response fields } else { var gr = new GlideRecord(&#39;table_name&#39;); gr.addQuery(&#39;sys_id&#39;,sysid); gr.query(); while (gr.next()){ if (sbj.indexOf(&#39;Accepted&#39;) &gt; -1){ gr.u_invite_response = 3; } else if (sbj.indexOf(&#39;Tentative&#39;) &gt; -1){ gr.u_invite_response = 5; } else if (sbj.indexOf(&#39;Declined&#39;) &gt; -1){ gr.u_invite_response = 7; } var responsenotes = gr.u_invite_response_notes; var bslice = body.indexOf(&#39;BEGIN:VCALENDAR&#39;); if (bslice == -1) { gr.u_invite_response_notes = body + &#39;\n\n&#39; + responsenotes; } else if (bslice &gt; 0) { var sbody = body.slice(0, bslice); gr.u_invite_response_notes = sbody + &#39;\n\n&#39; + responsenotes; } gr.update(); } } } </code></pre> <p>I don&#39;t see the response showing up in the email log for ServiceNow</p> <p>Make sure that your instance&#39;s email address is set as the organizer in the iCalendar code. If you hardcoded this to your production instance, you will need to change it before you send a response to the calendar invite</p> <p>I see the email in the log, but nothing is happening.</p> <p>Check the email under System Mailboxes -&gt; Inbound -&gt; Received. You&#39;re able to watch and see as the Inbound Email Actions are taking any action on the received email or not. If there is a message that says &quot;Skipping &#39;Your Inbound Email Action Name Here&#39;&quot;, then that means the conditions are not lining up correctly.</p> <p>If you can verify in the system log that the inbound email action is running, then that means something is wrong in the script. I recommend using gs.log to verify things like the sys_id of the record you want to update and make sure all 32 characters of it are there, if any IF statements you put is are running correctly, and that any fields you have set are named correctly. Also don&#39;t forget your .update() to update the record!</p> <p>Hope this has been useful and informative. Good luck to everyone that wants to use this, and please feel free to add any suggestions or tricks you run across!</p> <p>Feel free to comment, or find me over on the Community or LinkedIn if you have any further questions/suggestions/ideas. Thank you!</p> <p>[<img src="https://community.servicenow.com/community?id=community%5Fuser%5Fprofile&user=3d621a69dbd81fc09c9ffb651f961931" alt="image"><a href="https://www.linkedin.com/in/bradfordshelley" target="_blank" rel="noopener"><img src="https://sites.google.com/site/bradfordshelley/home/servicenow/LinkedIn-Logo-02.png" alt="https://www.linkedin.com/in/bradfordshelley"></a></p> <p>Labels: </p> <ul> <li><a href="https://community.servicenow.com/community/developer-articles/tkb-p/developer-kb/label-name/multiple%20versions?labels=multiple+versions" target="_blank" rel="noopener">Multiple Versions</a></li> <li><a href="https://community.servicenow.com/community/developer-articles/tkb-p/developer-kb/label-name/scripting%20and%20coding?labels=scripting+and+coding" target="_blank" rel="noopener">Scripting and Coding</a></li> </ul> <p><a href="https://community.servicenow.com/now-platform/latest-release.html" target="_blank" rel="noopener"><img src="https://community.servicenow.com/community/s/html/assets/Xanadu-Community_Banner_1_314x314.jpg" alt="image"></a></p>

View original source

https://www.servicenow.com/community/developer-articles/servicenow-calendar-invites/ta-p/2304049