Does your organisation use Microsoft Bookings? Do you also use Customer Insights – Journeys? What better way to keep your branding consistent than to send all communication about meetings scheduled via Bookings than to send notifications out via a Real-time Journey. In this post, I will look at how you can achieve this with ALL communications going out via a Journey rather than some from Bookings. This means you can totally control the look and feel of all of the emails, and take them out of the journey if the meeting is cancelled too. Let’s jump in!
This post expects you will have some level of understanding of Microsoft Bookings already. If you don’t know much about it, you can check out my previous posts on the topic here.
The first thing you will need to do is make sure none of the customer notifications will go out. This involves a few things. Make sure each service has the box unticked for send a meeting invite to the customer, in addition to the confirmation email. Also, make sure you don’t have any reminders or follow up emails set to go out.
Now, remember the setting about that was for sending a meeting invite in addition to the confirmation email? Well there isn’t a place to turn off the confirmation email. However, if we go in to the customised fields section, we can deselect the customer email. This will prevent the auto generated confirmation email from going out. Add in a customised field for Email instead. Make this required.
We need at least one trigger. This one is for when a Microsoft Bookings appointment is created. For this, it’s up to you what you want to capture to send to the person who made the booking. In my example I want to capture the start and end date/time, the name and email of the staff member that the booking was made with, the name of the service booked, and then two values for the join URL (for Teams) and the URL for a custom ICS filed we will create so someone can add it to their calendar. I am also linking mine to Contacts rather than Leads. Save your trigger and make it live.
This second trigger isn’t required, but having it will allow us to take someone out of the Journey if their meeting is cancelled. For this, no values are needed, just set it for the same data type as the trigger above.
For your emails, you can create one for when the booking is made, then however many reminders you want and even one you might want to send if the booking gets cancelled, the choice is yours. Here is an example of my confirmation email, where all of the dynamic content can be pulled through from the new trigger I created.
Each of the values is mapped to values from the Microsoft Bookings Process trigger, and of course the First name is mapped from the Contact.
Next we need our first flow to be built in Power Automate. It starts when an appointment is Created using the Microsoft Bookings connector. If you aren’t sure how to connect to your calendar, check out this post which will explain how, and this one for full details on what you can and can’t do with the connector.
Next, we are going to use a Filter array action to get the answer to our custom Email question. The From array should be the Custom Question Answers.
@triggerOutputs()?['body/CustomQuestionAnswers']
Then the value should be the Question is equal to Email (or whatever you called your email field).
@item()?['Question']
Next, we need to add an initialise variable step to store the Contact GUID. We will either find an existing one for the person that makes the booking, or will create a new one. Leave this blank.
Now we have a List rows step to look for an existing Contact using the email address provided on the Booking. You can use the following expression to get the Answer from the filter step.
emailaddress1 eq '@{first(body('Filter_Email_Address'))?['Answer']}'
Now we will do a condition to check and see if the number of Contacts found is zero. If it is, we will add a new Contact, otherwise we will take the GUID of the contact and add it to our Contact GUID variable. To check the number of records, use this expression. Note that if you did not rename your List rows step, you will need to check where it shows Find_Existing_Contact to whatever you named it.
length(body('Find_Existing_Contact')?['value'])
If it is zero, we go down the Yes path and use an Add row step to create a new Contact. Likely the only values you will have are the email and the name. For the email use this expression:
@{first(body('Filter_Email_Address'))?['Answer']}
For the first and last name, they are stored as one value and that can’t be changed in Microsoft Bookings. So we need to do some splitting up (which won’t always be accurate) to figure out what to populate in to the fields on the new Contact. For the first name, we are going to split based on spaces and take the first part of the name.
@{split(triggerOutputs()?['body/CustomerName'], ' ')[0]}
For the last name, we will take the rest of the value and put it in the last name field.
@{split(triggerOutputs()?['body/CustomerName'], ' ')[1]}
Now we will add the Contact in to the Contact GUID. If it is a new Contact we can use the GUID direct from the step above on the YES path. If it is an existing Contact, we need to get the GUID using an expression. This will give us the first GUID found, which is much smoother than adding an Apply to each (which will happen if you try to pull the GUID directly from the List rows step).
@{first(outputs('Find_Existing_Contact')?['body/value'])?['contactid']}
Moving on, now we will need to do something with the time zone so that we can be sure it’s sent correctly to the person who made the booking. Your Booking Page settings has a timezone, so all bookings made will have used that same timezone. We need to get that, and then make sure for our ICS file it is set to UTC using the time for that. This means when the ICS file is added to a calendar, it will show up with the correct time. The timezone is passed through from the booking and we can use it in the flow to find the equivalent timezone in Dataverse. Use this logic as the filter in a list rows step looking for Time Zone Definitions.
(standardname eq '@{triggerOutputs()?['body/CustomerTimeZone']}')
Now we need to use the convert time zone action twice, once for the start time and once for the end time of the booking. Use those as the base time, and set the format string as yyyyMMddTHHmmss. The destination timezone should be UTC Coordinated Universal Time. Set the source timezone as the one found from the List rows step like you see below. Again, if you didn’t rename the step above to be the same as mine, adjust the part that says Find_Time_Zone.
@first(body('Find_Time_Zone')?['value'])?['standardname']
The next part is to add an initialise variable step for a new string variable. This will be used to build out an ICS file. There is a great post here from Manuel Gomes that explains some of the structure, and this post from me where I have used this process before to create an ICS file. Some key points, an ICS file needs a unique id, so use the Self Service App ID that you can get from the bookings trigger as this will always be unique. You can also find the Service Name which is the name of the service selected when the booking was made, and the Join Web URL which will be for Teams.
Now we need to add the file somewhere so that it can be accessed by the person who made the booking. We do this in a couple of steps. This first is Add a row for Dataverse and pick the Files table. It’s the msdyncrm_files one you need (there are multiple) which you can check by clicking on the three dots then peak code to make sure you selected the right one. Give the file a name. I used the Customer Name then the Service Name, then end it in dot ics. If you expand to show advanced options, add this in to the content type: text/calendar. This process is actually adding the files to the asset library in Customer Insights – Journeys. If you want to use a different storage option, by all means you can, but the link to the file needs to be accessible to the outside world, meaning you can’t use any kind of internal storage.
The step above just created the record that stores the file, but we still need to add the file to it. Add an action below called ‘Upload a file or an image’ and pick the same Files table. The Row ID should come from the step you did above. Set column name to filecontent and the Content should be the ICS File variable you created. Open the advanced options and use the same name you added above in to the Content name,
The last step is that we need to get the URL for the image file. Add in a get row by ID step and use the file identifier from the create file record step, then select the msdyncrm_blobcdnuri field as that is the only one we need.
FINALLY we reach the end of the flow. This one is the Perform an unbound action step from the Dataverse connector. From here, you should be able to find the name of your trigger in the Action Name list. This does take a while to open, and it is a good idea to type in msdynmkt_ first to filter it a bit. Pick the action and then populate all of the values you added to the trigger. Hopefully it’s obvious what I used by looking below, but for the staff member information, those are stored a bit differently so we will use expressions for those:
@{first(triggerOutputs()?['body/StaffMembers'])?['EmailAddress']}
@{first(triggerOutputs()?['body/StaffMembers'])?['DisplayName']}
Although adding a binding id isn’t required, doing this will allow us to use another trigger if the booking gets cancelled and take them out of the journey. Remember I said there is a unique Self Service App ID value? We can use this along with ms_booking_id/ to pass it through. We will do the same thing on the other trigger journey so the two can match up. Save your flow. You can create a new booking and it should fire, but we have no Journey yet. Just make sure the flow is successful.
We need a second flow that will start when an Appointment is cancelled. So connect back to your calendar, and copy the same initial step where you are filtering on the custom questions to find the email address.
Again, same list rows step but this time there is no need for us to create a step to determine if we need to add a new Contact or not.
We now end with another Perform an unbound action step using our Microsoft Bookings Cancelled trigger. This has only five values required. The first two are the utcNow() expression. the second two are the Contact GUID we got from the List rows step above. You can use this:
@{first(outputs('Find_Existing_Contact')?['body/value'])?['contactid']}
Finally, we need to match up the bindingid using the exact same format as you used in the first flow. For mine, I used this.
ms_booking_id/@{triggerOutputs()?['body/SelfServiceAppointmentId']}
Hang in there, we are almost done. Now we need the journey. This should start when your new custom Microsoft Bookings Process trigger fires. You can then add in the initial confirmation email you want to send.
If you want to send a reminder the day before the meeting, do this using an Attribute branch, then check the Start Information from the trigger on your branch. You can use Is, then Relative Date of tomorrow. If it is tomorrow, they will go down that branch and send them the reminder. If it isn’t tomorrow (they could book on the same day potentially), no reminder will be sent.
On the exit panel of the Journey, add in the Microsoft Bookings Cancelled trigger.
You will only see logic of people exiting the journey via the exit trigger if your Journey is longer than one email. Here on the wait time, I can see that out of the 5 people who went through it, 4 were processed through the wait, but 1 exited because the exit event happened (the booking was cancelled). Perfect!
Here we can see our lovely confirmation email with all of the details, a button to get the ICS file to add to calendar, and a direct link to join the meeting in Teams. One thing you might also want to add is a flow to remove all of your ICS files when they are no longer needed (after the booking has happened). Otherwise they will sit in your asset library when they are no longer relevant.
Original Post http://meganvwalker.com/send-all-microsoft-bookings-notifications/