Creating a meeting notice in Outlook from the Notes client

Another task in our migration to Outlook as the mail client is creating Outlook calendar entries and meeting notices directly from the Notes client. Fortunately, it’s been two years since I wrote about how to do this in the UI in Notes, so I don’t feel like that was wasted time. I was exciting to solve the problem and… oddly enough, solving this one was fun as well. It helped that creating an iCal entry is far simpler than the gyrations we had to go through to create one in Notes. As noted previously, there  aren’t a whole lot of required values to generate in order to have an ICS file that you can open in the UI as a meeting notice/calendar invite.

SUMMARY:2017 Q2 QPR: Agribusiness Competitiveness Enhancement via file

Now, keep in mind that this iCal file is a mere fragment. If you sent that file to someone, they get the same behaviour you get — it thinks they’re the meeting organizer and doesn’t save it to their calendar unless they send the ‘update’. The key parameter we leave off is that we don’t set METHOD, since setting that to PUBLISH or REQUEST proved problematic in the Outlook client. If we leave it off, Outlook will allow us to treat it like a brand new calendar entry we’ve created, except that the send button will say ‘Send Update’.

So, let’s review those values in our fragment…


First, the calendar and event objects are encapsulated. Nothing fancy there.


Meeting times

Then we have our start and end times, formatted with date first (YYYYMMDD) then a separator (T) and then the time (HHMMSS). You can include time zone information, but we’re creating this in Outlook and allowing the UI to finish everything for us. So, if the user wants to change the time zone, they can do that in Outlook.



The one required value for our needs in the attendees is the mailto value. Without that, it won’t know who to send the invite to and it simply ignores any other item in that list.


ROLE is not required and can be CHAIR, REQ-PARTICIPANT (required participant), OPT-PARTICIPANT (optional participant) or even NON-PARTICIPANT (for FYI only).

PARTSTAT is not required. There are several values for an attendee in a VEVENT for their participant status, but we’re only concerned with two. Either “NEEDS-ACTION” for attendees that we don’t know when we create the meeting notice whether they’ve agreed to attend or “ACCEPTED” which we’d typically only use for the person creating the meeting notice.

CN is, of course, familiar to us as Notes developers, but it applies here to whatever will be displayed as the attendee name. In my experience, Outlook can parse the abbreviated name and display just the attendee’s common name. That might be our Outlook configuration, but I would assume it’s common.

RSVP would be either true or false, indicating whether you want a response from the attendee. In my case, we always want it from the attendees, other than the current user.

Title and description

I got fooled by this one. In my sample ICS files, I thought there was just an odd carriage return, but the DESCRIPTION value is basically the body or details of the event, while the SUMMARY is what appears in the subject line for the meeting.

SUMMARY:2017 Q2 QPR: Agribusiness Competitiveness Enhancement via file

Meeting ID

I’m guessing that Outlook computes the unique meeting ID itself, but in my code, I generate from the Notes document’s unique ID and then, in order to ensure that subsequent meetings concerning the same document get different IDs, I’m appending a creation time-stamp.


So, the agent I wrote that generates the new meeting notice is pretty straight-forward. The getEmailAddress function was described and detailed in a prior blog post and my Utilities script library only provides the logging functions here. Like my mailto agent, this one relies on the creation of a file in the Notes data directory and opening it using a browser.

The agent

First, you can look over the main part of the agent…

	Agent (Send Calendar Invites)
	Created Jun 20, 2017 by David Navarre/DAI
	Description: This Agent creates a calendar invite, listing participants and optional participants
Option Public
Option Declare
Use "Utilities"

Dim session As NotesSession
Sub Initialize
	Dim ws As New NotesUIWorkspace
'	Dim thisdb As NotesDatabase declared in Utilities script library '
	Dim uidoc As NotesUIDocument
	Dim qprdoc As NotesDocument
	Dim history As NotesRichTextItem
	Dim chairName As NotesName
	Dim recipientName As NotesName
	Dim projectName As Variant
	Dim fiscalYearAndQuarter As Variant
	Dim participants As Variant
	Dim participantsOptional As Variant
	Dim subject As String
	Dim answer As Variant
	Dim reason As String
	Dim unid As String
	On Error GoTo errorhandler
	set session = New NotesSession
	Call StartAgentLogging ( session )

	If ( openAddressBooks () ) Then
		agentLog.Logaction("Address books opened")
	End If

	Set thisdb = session.CurrentDatabase
	Set uidoc = ws.CurrentDocument

	reason = "This will create a meeting invite for you to send to participants."
	If uidoc.Editmode Then
		reason = reason + Chr$(10) + "The QPR will switch to read-only mode."
		reason = reason + Chr$(10) + "If you close and re-open it, you can edit it again."
	End If
	reason = reason + Chr$(10) + "Continue?"
	answer = ws.Prompt ( PROMPT_YESNO, "Continue?", reason )
	If answer = 0 Then
		Exit Sub
	End If	
	If uidoc.Editmode Then
		Call uidoc.Save()
		uidoc.Editmode = False
		Set qprDoc = uidoc.Document
		unid = qprDoc.Universalid
		Call uidoc.Close(True)
		Set qprDoc = thisdb.Getdocumentbyunid(unid)
		Set uidoc = ws.Editdocument(False, qprDoc, True)
		Set qprDoc = uidoc.Document
	End If
	Dim fileName As String
	Dim dataDirectoryPath As String
	Dim url As String
	Dim fileNumber As Integer
	fileNumber = 1
	dataDirectoryPath = session.Getenvironmentstring("Directory", True)
	fileName = dataDirectoryPath & "\QPRInvite.ics"
	Open fileName For Output As fileNumber
	Print # fileNumber, {BEGIN:VCALENDAR}
	Print # fileNumber, {BEGIN:VEVENT}
	Print # fileNumber, {DTSTART:} & getMeetingTime ( "Start", qprDoc ) '20170620T211500
	Print # fileNumber, {DTEND:}  & getMeetingTime ( "End", qprDoc ) '20170620T221500

	' Chair '
	Set chairName = New NotesName ( session.Effectiveusername )
	' when you send the invite from Outlook, it makes you the chair '
	' this line is here to show how you would format an attendee line for the chair '
	' Print # fileNumber, {ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED;CN="} & chairName.Abbreviated & {";RSVP=TRUE:mailto:} & getEmailAddress ( chairName.Abbreviated ) '
	' Required participants '
	participants = qprDoc.Getitemvalue ( "Participants" )
	ForAll entry In participants
		Set recipientName = New NotesName ( entry )
		If Not ( chairName.Abbreviated = recipientName.Abbreviated ) Then
			Print # fileNumber, {ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN="} & recipientName.Abbreviated & {";RSVP=TRUE:mailto:} & getEmailAddress ( recipientName.Abbreviated )
		End If
	End ForAll
	' Optional participants '
	participantsOptional = qprDoc.Getitemvalue ( "ParticipantsOptional" )
	ForAll entry In participantsOptional
		Set recipientName = New NotesName ( entry )
		Print # fileNumber, {ATTENDEE;ROLE=OPT-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN="} & recipientName.Abbreviated & {";RSVP=TRUE:mailto:} & getEmailAddress ( recipientName.Abbreviated )
	End ForAll

	projectName = qprDoc.Getitemvalue("ProjectName") 
	fiscalYearAndQuarter = qprDoc.Getitemvalue("FiscalYearAndQuarter") 
	subject = fiscalYearAndQuarter(0) & " QPR: " & projectName (0)
	Print # fileNumber, {DESCRIPTION:} & subject ' this is the body of the message
	Print # fileNumber, {SUMMARY:} & subject ' this is the meeting name

	' assign a unique ID to meeting using the unid of the document with the current date-time appended '
	' in case user creates multiple meetings for the same QPR '
	Print # fileNumber, {UID:} & qprdoc.Universalid & getMeetingTime ( "Now", qprDoc ) 

	Print # fileNumber, {END:VEVENT}
	Print # fileNumber, {END:VCALENDAR}
	Close # fileNumber
	url = "file:///" & fileName
	Call ws.Urlopen(url)
	Set history = qprDoc.Getfirstitem("History")
	Call history.Appendtext(Now & " - Meeting notice created by " & session.Commonusername)
	Call history.Addnewline(1, True)
	Call qprDoc.Replaceitemvalue("NoticeFlag", 1)
	Call qprDoc.Save(True, False)

	Exit Sub
errorhandler:' report all errors in a messagebox '
	reason = "Error #" & CStr (Err) & " (" & Error & ") on line " & CStr (Erl)
	MessageBox reason, 16, "Error"
	Call agentLog.LogAction ( reason )
	Resume exiting ' transfers control to the exiting label	
End Sub


The getMeetingTime function just returns the requested date-time in the format YYYYMMDDTHHMMSS, so it can be included in the creation of the ICS file.

	Function getMeetingTime
	Description: This Function returns a string in the format YYYYMMDDTHHMMSS
		If it is the start time, the values from the source document are used 		-- 20170622T211500
		If it is the end time, it is adjusted one hour later					 	-- 20170622T221500
		If it is the "Now" time, it returns a string for the current date and time	-- 20170620T094326
Function getMeetingTime ( startOrEnd As String, qprDoc As NotesDocument ) As String
	Dim thisNotesDateTime As NotesDateTime
	Dim qprDate As Variant
	Dim qprTime As Variant
	Dim timeString, dateString As Variant
	Dim reason As String	

	On Error Goto errorhandler

	Set qprDate = qprDoc.Getfirstitem("QPRDate")
	Set qprTime = qprDoc.Getfirstitem("QPRTime")
	dateString = qprDate.Text
	timeString = qprTime.Text
	Set thisNotesDateTime = New NotesDateTime ( dateString & " " & timeString )
	Select Case startOrEnd
		Case "End"
			Call thisNotesDateTime.AdjustHour (1)
		Case "Now"
			Set thisNotesDateTime = New NotesDateTime ( Now )
		Case else	
			' keep thisNotesDateTime as set on the source document '
	End Select
	dateString = thisNotesDateTime.DateOnly
	timeString = thisNotesDateTime.TimeOnly
	getMeetingTime = CStr ( Year ( dateString ) )
	getMeetingTime = getMeetingTime & Right$ ( "0" & CStr ( Month ( dateString ) ), 2 )
	getMeetingTime = getMeetingTime & Right$ ( "0" & CStr ( Day ( dateString ) ), 2 )
	getMeetingTime = getMeetingTime & "T"
	getMeetingTime = getMeetingTime & Right$ ( "0" & CStr ( Hour ( timeString ) ), 2 )
	getMeetingTime = getMeetingTime & Right$ ( "0" & CStr ( Minute ( timeString ) ), 2 )
	getMeetingTime = getMeetingTime & Right$ ( "0" & CStr ( Second ( timeString ) ), 2 )

	Call agentLog.LogAction ( "-------" ) 
	Call agentLog.LogAction ( "-------" ) 
	Exit Function
errorhandler:' report all errors in a messagebox '
	reason = "Function getMeetingTime: "
	reason = reason & "Error #" & Cstr (Err) & " (" & Error & ") on line " & Cstr (Erl)
	Messagebox reason, 16, "Error"
	Call agentLog.LogAction ( reason )
	Resume exiting
End Function

While this did take me a few days to sort out, I’m pretty happy with the result. Our configuration has users sharing one “migration” mail file, so that users who are already on Outlook still retain a mail file and can send email. Unfortunately, that means any email from them that we create in the UI is going to have values pointing back to the “migration” mail file. I spent my first few days on this trying to spoof the by changing Principal, ReplyTo, $InetAddress and Chair when sending via Notes calendaring. While changing Chair did make it appear to come from the current user, it always displayed the email address from the “migration” mail file. It might have been getting caught in our spam filter on the way to Outlook, as my test user on Notes was still receiving the notices. Nonetheless, by switching to using Outlook as the UI, it not only took away that problem, but was far simpler and future-proofed my application. As I look at these tools I’ve created in LotusScript to generate mail messages and calendar entries, I know that it’s but a short step to doing them in server-side Javascript or maybe in Java.

There is hope for the Notes gurus of old. We just have to keep learning!

iCal RFC (documentation?)

Categories: Old Notes, Utilities | Tags: , , , , , | Leave a comment

Post navigation

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at

%d bloggers like this: