From Mrityunjoy Chowdhury: Clone Recurring Shift along with Shift Recurrence Pattern:


In Spring ’22 Salesforce Release – Scheduler Product introduced the Manage Service Resource Availability by Using Shifts feature which gave flexibility for service resources to work flexible hours instead of the Service Territory Operating Hours. This feature was extended in Winter ’23 with the flexibility to create Recurring Shifts – which gave an easy way for
Territory Managers and Service Resources to create multiple shifts with a single record data entry.

This was made possible by capturing the recurring pattern of the shift during data entry.

And Salesforce saves this recurring shift as a single record with Type field as “Recurring”.

Salesforce Scheduler saves the recurrence pattern when the original recurrence shift record is saved. The information is saved in “RecurrencePattern” field on the same Shift record. An example pattern as saved on the record


This information corelates to what gets saved as shown in the screenshot – Weekly recurring Event which occurs every Monday, Wednesday and Friday for the next 4 occurrences and repeats every week.

NOTE: Salesforce Scheduler considers the recurrence period in the service territory’s timezone. The period of recurrence is limited to 120 days. For example, you create a recurring shift on July 1, 2022. You can choose an end date that’s either less than or equal to 120 days from July 1, 2022, or the number of occurrences that are repeated within 120 days from July 1, 2022.

Cloning Shift Records

Scheduler product also gave the option to clone shift records. When a user tries to clone a Shift record with the Type field value set to Recurring, the new record creation screen that opens, Salesforce prepopulates the Start and End Times of the Shift, Status, Service Territory, Service Resource and the Time Slot Type fields. You will notice, it does NOT prepopulate the recurring Pattern (at this point of time).

This is a limitation in the current scheduler product, until this feature gets included in future (Roadmap).

In this blog we are going to look at an option to clone the Shift along with its recurring Pattern using Apex

Cloning Shift Records with Recurring Pattern

We want to keep it simple here in this blog. This is just a quick and dirty work around, but feel free to take it to the next level. As you have noticed when using the Clone button on the Shift (with type as recurring) – the new shift screen that opens has all the information from the previous record copied, except for the Recurrence pattern.

The copying of recurrence pattern is handled in the trigger which will copy it from the Original Shift where we are cloning the record from.

Use the sample trigger code below on the Shift Object to copy the recurrence pattern

trigger CloningRecurringShift on Shift (Before insert) {
for(Shift sh : Trigger.New){
if(sh.Type =='Recurring' && sh.IsClone()==true && sh.RecurrencePattern=='RRULE:INTERVAL=1;'){
String sourceId = sh.getCloneSourceId();
Shift orginalShift = [Select RecurrencePattern from shift where ID=:sh.getCloneSourceId()];
System.debug('Orginal Shift recurring patternt ' + orginalShift.RecurrencePattern);


Note: If updating the other fields (not the recurrence type section) that were copied over when you tried to Clone, the saved record will consider the new updated values.

Demo recording

Coming soon


From Chris Albanese: Scheduler: Multi-Resource Scheduling – Default additional resource(s)


Often times customers who use Multi-resource scheduling would like to default or automatically select the additional resource(s). For example, when scheduling a meeting with your portfolio advisor, you would like her assistant automatically added to every appointment and you want the available time slots to reflect this.

With a few simple flow configurations and a formula field, you can easily default the additional resource selection by setting a value for a variable called serviceResources. In the 2nd screen shot below, Russell was automatically selected because serviceResources had a value in it.

The variable called serviceResources is usually empty at this point. If you set a value for it, you can default the multi-resource selections.

Russell is pre-selected when entering this screen.

Video of it in action

Check out a quick demo video

Solution Overview

In this solution, a Service Resource can optionally have an Assistant Service Resource assigned to their record. If there is a value defined here, this Assistant SR will be defaulted as an additional required resource in the Multi Resource section of the time slot selection screen.

A few get records steps are added as well as a formula field to set the value for the serviceResources variable in the flow.

Object Model Changes

Add a new lookup field on the Service Resource object to itself. This field is called Assistant (Assistant__c). There are so many ways to tackle this, such as defining Primary and Secondary Resources on the Account object, or creating a junction object between Account and Service Resource or Service Resource and Service Resource to provide a more flexible design, as well as other designs. For simplicity sake, I added the lookup field on the Service Resource object to itself. In the second screenshot below, Connie has an assistant named Russell.

Flow Changes

Added Steps

If the selected Resource has an Assistant Service Resource, then using get Record steps, fetch the Resource’s record, related User Record and relevant Service Territory Member Record. For the Assistant Service Resource, fetch the same records.

Using a formula field, assign a value to the serviceResources variable.

Birds eye view – additional steps added after the candidate screen.

Zoomed in a little more – get SR, User and STM records for selected Candidate and Assistant. If the Assistant exists, use a formula field to set the value used by Multi-Resource Scheduling.

Formula field used in the set serviceResources assignment step

The formula is pretty simple – you just need to be particular with the open and closed quotes.

Let’s examine the elements in the formula

The variable called serviceResources is a JSON payload and for multi resource scheduling it contains 2 or more elements, with the first one being the primary resource and the second one (and possibly the third through fifth ones) being the additional resource(s). The bold text are comments I added for this blog and should not be in the formula you create.

{“UserPhoto”:”‘&{!get_Primary_User.FullPhotoUrl}&'”, Primary Resource Photo
“ServiceTerritoryMemberId”:”‘{!get_Primary_STM.Id}&'”, Primary Resource STM Id
“IsActive”:true, True
“ResourceType”:”‘&text({!get_Primary_SR.ResourceType})&'”, Primary Resource Type
“RelatedRecordId”:”‘&{!get_Primary_SR.RelatedRecordId}&'”, Primary Resource User Id
“Id”:”‘&{!get_Primary_SR.Id}&'”, Primary Resource Id
“Name”:”‘&{!get_Primary_SR.Name}&'”, Primary Resource Name
“sobjectType”:”ServiceResource”, Service Resource
“AttendanceType”:”Primary”}, Primary
{“UserPhoto”:”‘&{!get_Assistant_User.FullPhotoUrl}&'”, Assistant Resource Photo
“ServiceTerritoryMemberId”:”‘&{!get_Assistant_STM.Id}&'”, Assistant Resource STM Id
“IsActive”:true, True
“ResourceType”:”‘&text({!get_Assistant.ResourceType})&'”, Assistant Resource Type
“RelatedRecordId”:”‘&{!get_Assistant.RelatedRecordId}&'”, Assistant Resource User Id
“Id”:”‘&{!get_Assistant.Id}&'”, Assistant Resource Id
“Name”:”‘&{!get_Assistant.Name}&'”, Assistant Resource Name
“sobjectType”:”ServiceResource”, Service Resource
“AttendanceType”:”Required”} Required

Notes about the formula

  • If you use AttendanceType = Required, the additional resource’s availability is reflected in the time slots. If you make it “Optional”, then only the primary resource’s availability is reflected.
  • If you have additional resources, just add additional elements for them in the formula, modeled after the Assistant Resource. You may choose to do this in a loop to provide some flexibility
  • If you use Asset Service Resources, then there will be no photo
  • If you want to mandate that there is at least 1 secondary resource, you can test in a decision element that serviceResources contains either the word “Required” or “Optional” and redirect the user back to this screen if this is not true

Here’s the uncommented formula


Note: Make sure you have enabled the feature

Extended Working Hours for Privileged Customers

Problem Statement

Consider a bank with Operating hours 9AM to 5PM and a wealth manager Sam who works flexible hours 8AM to 6PM. During regular operating hours Sam is available to take any type of appointments, however for “Privileged” customers he doesn’t mind talking to them as early as 8AM and as late as up to 6PM (additional 2 hours – beyond the operating hours).

How do we let Privileged customers book appointments that ignore operating hours?
The answer might look simple – we do NOT want to use the Operating hours when it comes to privileged customers – instead use the service resources shifts.

But at the same time we should let normal customers to book appointments during regular hours, which means we need to use the Scheduler features of Shifts & Operating hours at the same time.

That’s exactly the reason for this blog – to explain how to achieve this by understanding the basics of scheduling policies, which are like the rules used by the Scheduling engine.

For every standard Scheduling Flow that does help with Appointment scheduling it is mandatory to use a scheduling policy (if ignored, the default one that is shipped with the product out of the box is used) – which is a way to define the rules we want to run the scheduling flow with. In certain business use cases it is necessary to use different combinations of these rules within a scheduling flow.

Lets try to understand some commonly used rules of scheduling policies before we solution the problem

What are Scheduling Policies?

Scheduling policies enforce one or more rules that help you find the best service resources for appointments. Rules that are used by the Scheduling Engine while calculating Time Slots are explained in detail in our help here. Combinations of these Rules define the policy that can be used for a specific appointment booking experience.

Here are 3 rules on the scheduling policy we will try to understand

  1. Use ONLY Operating Hours and Do NOT use Shifts
  2. Use Service Territory Member’s Shifts
  3. Use Service Territory Operating hours with Shifts

OPTION 1 – Consider ONLY Operating Hours and Do NOT consider Shifts

Operating Hours represent the time slots during which appointments can be scheduled. Administrators can define these timeslots specific to a timezone. Scheduler provides the flexibility to specify the Operating hours at a Service Territory or at a Service Territory Member level. Administrators have to mention either or both of them to let the Scheduler Engine calculate the precise availability of resources and timeslots.

Rules to use when we want to consider Service Territory Operating Hours as setup in the Scheduling Policy

If we use these rules on the Scheduling policy – every customer will ONLY see time slots from 9AM to 5PM. Even our advisor Sam will be ONLY available 9AM to 5PM.

OPTION 2 – Use Service Territory Member’s Shifts

Rules to use when we want to consider Service Territory Member’s Shifts INSTEAD of the Operating Hours

When the “Use service territory member’s shift” is checked alone on a scheduling policy – the scheduling engine will totally SKIP considering the Operating Hours set for the Service Territory Member (either at the service territory or the service territory member record level).

If we use these rules on the Scheduling policy – our advisor Sam will be available to take appointments from 8AM to 6PM. This is the rule we should be using for privileged customers.

OPTION 3 – Use service territory’s operating hours with shifts

Rules to use when we want to consider Service Territory’s Operating Hours ALONG with the Member’s Shifts.

When the “Use service territory member’s shift” is checked along with “Use service territory’s operating hours with shifts” on a scheduling policy – the scheduling engine will totally consider the intersection of shifts and service territory operating hours when determining the availability of service resources.

If we use these rules on the Scheduling policy – our advisor Sam will be available to ALL Customers from 9AM to 5PM. We can use this scheduling policy as default.

Using multiple scheduling policies in the same flow

Standard Scheduler Flow templates come with a global flow variables which can be customized as per our business use case and needs.

SchedulingPolicyName flow variable lets you specify the scheduling policy we intend to invoke from the flow.

Since in our example we want to use 2 different scheduling policies we can validate the customer tier (a custom field on the account object that maintains the customer tier information) and based on the tier information, we can specify the scheduling policy name in the flow.

Extending availability for Privileged Customers

This is how a customized scheduling flow (outbound new appointment in this example here) looks like and the steps 1 to 4 are the customizations done to update the flow (Details with screenshots for the 4 steps are shared below)

1. Get Records: Get Account Tier

2. Decision: Determine Account Tier

3. Assignment: Use Shifts – Account Tier is Privileged

4. Assignment: Use Shifts with Operating Hours – Account Tier is Normal

From Pratyush Kumar : Orchestrate Appointment Experience based on Language selected

Introduction :

This blog suggests a way that allows appointment booking with service resources who have support for a set of languages. Thus, we will be adding language support to our existing Appointment Booking flows.

Problem Statement:

Let’s say we have a Service Resource who can speak only a certain set of languages, say Hindi and English and another who can speak in only Chinese. Even though each of the resources have all the relevant skills for attending a Work Type, he/she will not be able to drive the appointment since they cannot speak a certain language.

By introducing language support we will try to eliminate this problem.

Solution Approach:

  1. We manage a multi-select pick-list custom field on the Service Resource Object which will store the list of languages the resources can support
  2. Add a selection to the flow that will display the languages – so that requestor can opt for languages he/she prefers to get the service in
  3. After selection of languages in flow, the FilterByResourceIds is populated by intersection of ( getAppointmentCandidates API call and SOSL to fetch resources supporting the list of languages)
  4. List the available service resources by filtering out those who know a specific language by leveraging “filterByResources”

Setup Steps:

Create a Global Value Set

Create a new Global Value Set , with a defined set of language values.

Screenshot 2022-11-16 at 9.09.20 AM.png

Add a Multi-Select Picklist Field on Service Resource

Add a multi-select picklist field on Service Resource which takes values from the earlier defined global value set.

Screenshot 2022-11-16 at 9.12.19 AM.png

Create an Apex Class

Add following code to create an Apex Class ( Note that this class method will be used to set the FilterByResourceIds parameter):

There are two ways in which we can create this action.:

  1. When there are many STM records for the selected ServiceTerritory, an extra API call can be used to reduce the scope of our query.

public class MultiLanguage {

   @InvocableMethod(label=’Get Service Resource Ids’)

   public static List<String> getServiceResourceIds(List<Input> inputs){

       lxscheduler.GetAppointmentCandidatesInput input = new lxscheduler.GetAppointmentCandidatesInputBuilder()


               .setTerritoryIds(new List<String>{







       String vString = lxscheduler.SchedulerResources.getAppointmentCandidates(input);

       List<Object> list1 = (List<Object>)Json.deserializeUntyped(vString);

       Set<String> test1 = new Set<String>();

       for(Object obj:list1){

           Map<String,Object> map1 = (Map<String,Object>)obj;

           List<Object> test2 = (List<Object>)map1.get(‘resources’);

           for(Object s:test2){




       System.debug(‘[RETURNED_RESULT]’ + test1);

       String languages = inputs.get(0).languagesSelected;

       List<String>languagesList = languages.split(‘;’);

       languages = ‘(\” + String.join(languagesList, ‘\’,\”) + ‘\’)’;

       List<ServiceResource> resources = DataBase.query(‘select id from ServiceResource where Languages__c includes ‘ + languages + ‘and id in :test1 and IsActive = true’);

       List<String> list2 = new List<String>();

       for(ServiceResource r:resources){



       return new List<String>{String.join(list2, ‘,’)};


   public class Input{


       public String serviceTerritoryId;


       public String workTypeGroupId;


       public String accountId;


       public String languagesSelected;



  1. In cases when the count of STM is not that large we can directly query without an extra API call.

public class MultiLanguage2 {

   @InvocableMethod(label=’Get Service Resource Ids2′)

   public static List<String> getServiceResourceIds(List<Input> inputs){

       String languages = inputs.get(0).languagesSelected;

       List<String>languagesList = languages.split(‘;’);

       languages = ‘(\” + String.join(languagesList, ‘\’,\”) + ‘\’)’;

       String serviceTerritoryId = inputs.get(0).serviceTerritoryId;

       List<ServiceTerritoryMember> resources = DataBase.query(‘select ServiceResource.Id from ServiceTerritoryMember where ServiceResource.Languages__c includes’ + languages + ‘and ServiceResource.IsActive = true and ServiceTerritory.Id =:serviceTerritoryId’);    

       List<String> list2 = new List<String>();

       for(ServiceTerritoryMember r:resources){



       return new List<String>{String.join(list2, ‘,’)};


   public class Input{


       public String serviceTerritoryId;


       public String workTypeGroupId;


       public String accountId;


       public String languagesSelected;




  1. Clone the existing Outbound New Appointment Flow.
  2. Add following Elements:
    • Variable : selectedLanguages of type Text.
    • Screen : Language Screen ( This screen will show the picklist values of Global Value Set to choose from set of languages )
      • Add a Multi-Select Picklist Component to the Screen
      • Under the Choice setup, add a New Choice Resource with theses values 
Screenshot 2022-11-16 at 9.29.51 AM.png
  •  Add Label and API Names to this screen 
Screenshot 2022-11-16 at 9.33.49 AM.png
  • Assignment( Assign Selected Languages):
Screenshot 2022-11-16 at 9.36.28 AM.png
  • Apex Action ( languageAction )
    • Choose Uncategorized under Filter By Category
    • Add the apex action which was created earlier i.e “Get Service Resource Ids
Screenshot 2022-11-16 at 9.41.26 AM.png
  • Decision : (ShowCandidateScreen)
Screenshot 2022-11-16 at 9.43.38 AM.png
  • Screen(Error) : This will show error if there exists no resource which supports the languages selected.
Screenshot 2022-11-16 at 9.47.16 AM.png
  • Order the flow as suggested in the image below
Screenshot 2022-11-16 at 9.20.20 AM.png

Video: (Working Solution)

From Adwait : Email Appointment Confirmation with Video Conferencing Information (3rd Party) included


This blog will cover the scenarios for a salesforce user to integrate third party web conferencing tools which provide video appointments (like zoom, WebEx, MS team) and send an email confirmation including those details for the recipient to attend the meeting.

We will follow the following steps in order :

  • Integrate salesforce scheduler with any third party video web conferencing provider.
  • Create a record triggered flow on Service Appointment that will call external API’s and obtain the Meeting URL.
  • Fetch the associated lead email address and service resource name.
  • Configure this flow to stamp the service resource name on the comments field and meeting URL on AppointmentBookingInfoURL field of the service appointment.
  • Create an apex action to send email to the leads email address using appropriate email template.

We will be covering both the scenarios

  • Service Appointment is booked used AppointmentType as “Video”.
  • Service Appointment is booked using EngagementChannelType with ContactPoint as “Video”.

Now, we will be following these steps but before that go through the key points and always keep them in mind.

Key Points

  • AWS Chime and other third party providers(webEx, MS Team etc) cannot be used in the same org.
  • In the steps ahead we will be using comments field on Service Appointment to store service resource name, this is just a working example you can also create a custom field for that purpose but don’t forget to edit the email template if you are using a custom field for this purpose.
  • While using third party providers after the service appointment is completed the status of the service appointment should be changed manually.
  • Third Party Integration Customer can use only one Zoom or any other provider user to setup and have to use the same common credentials to initiate the call.
  • Always make sure that you provide a valid email address for each unauthenticated user. For more information refer to this doc

Appointment is booked using Appointment Type “Video”

Integrating your org with any third party video web conferencing provider

One can follow these blogs for integrating orgs with different providers:

After integrating your org with the web conferencing provider of your choice we need to create a record triggered flow.

**As rest of the steps are common, For the purpose of this blog we have integrated our org with zoom.

Create a Record -Triggered flow on Service Appointment

  • Go to Setup → Flows → New Flow
  • Click on Record-Triggered Flow and Create
  • Select Service Appointment as Object.
  • Click “A record is created” radio button.
  • From the Condition Requirements dropdown select All Conditions Are Met (AND).
  • Add condition AppointmentType equals “Video”.
  • Click Actions and Related Records.
  • Click the “Include a Run Asynchronously path to access an external system after the original transaction for the triggering record is successfully committed” checkbox at bottom and click done.
  • Select Free-Form from the drop down at top
  • Open Developer Console in a new tab and create a new class “GetZoomMeetingURLwithInput” and use the code provided below inside the class body(this class will provide the meeting URL).
public class GetZoomMeetingURLwithInput {
   @InvocableMethod(label='Get Zoom Meeting URL with Input' callout='true')
   public static List<String> makeApiCalloutwithInput(List<String> appointmentId)
       HttpRequest req = new HttpRequest();
       Http http = new Http();
       //Setup the Endpoint and append the name of the file
       req.setHeader('Accept-Encoding','gzip, deflate, br');
       req.setBody('{"topic": "test create meeting","type": "1"}');      
       HTTPResponse res = http.send(req);
       System.debug('Response Body: '+res.getBody());
       /* Parse Response */
       JSONParser parser = JSON.createParser(res.getBody());
       String webLink;
       webLink = 'ZoomNotSetup';
       while (parser.nextToken() != null) {
       if ((parser.getCurrentToken() == JSONToken.FIELD_NAME) &&
       (parser.getText() == 'join_url')) {
           webLink = parser.getText();
           System.debug('join_url= ' + webLink);
   return new List<String>{webLink};

**This piece of code is specific for Zoom and you can refer to the blogs mentioned above for other web conferencing providers.

  • Go to the flow builder again and drag and drop one action element from the left hand side menu.
  • Select “Type” from Filter by drop down.
  • Click on “Apex Action” in the left side menu.
  • Select “Get Zoom Meeting URL with Input” in the Action drop down.
  • Enter “Get Meeting URL” in the label field and “Get_Meeting_URL” in the API Name .
  • Click on the Don’t include button.
  • In the appointmentId field select {!$Record.Id}.
  • Expand the Advanced section and Click the “Manually assign variables” checkbox .
  • Click on the output field input box and Click on the New Resource option.
  • Select “Variable” as Resource Type.
  • Enter “MeetingLink” in the API name field.
  • Select Data Type as “Text”.
  • Click on “Available for output checkbox“ and Click Done.
  • Click Done again for the Apex Action.
  • Now, Connect the start element with the Apex Action element and Select “Run Asynchronously” as scheduled path
  • Now, Your flow will look something like this.

Fetch recipient E-mail address from the parent lead record

  • Drag and drop get records element on the flow builder.
  • Enter “Get Lead Email” in the label field .
  • Enter “Get_Lead_Email” in the API Name field.
  • Select “Lead” as Object.
  • From the Condition Requirements dropdown select All Conditions Are Met (AND).
  • Add condition Id equals {!$Record.ParentRecordId}.
  • Click “Only the first record” radio button.
  • To store the value of record fetched create a new resource of type “Record” by clicking on the new resource from the Record input field.
  • Select “Variable” as Resource Type.
  • Enter “LeadRecord” in the API name field.
  • Select Data Type as “Record”.
  • Select “Lead” as Object and click Done.
  • Select “Email” from the field drop down
  • Click on “When no records are returned, set specified variables to null.” radio button and click Done.
  • Connect the apex action with the get records element.

Fetch service resource name

  • Drag and drop get records element on the flow builder.
  • Enter “Get assigned resource” in the label field .
  • Enter “Get_assigned_resource” in the API Name field.
  • Select “Assigned Resource” as Object.
  • From the Condition Requirements dropdown select All Conditions Are Met (AND).
  • Add condition ServiceAppointmentId equals {!$Record.Id}.
  • Add condition IsRequiredResource equals {!$GlobalConstant.True}.
  • Click “Only the first record” radio button.
  • To store the value of record fetched create a new resource of type “Record” by clicking on the new resource from the Record input field.
  • Select “Variable” as Resource Type.
  • Enter “ServiceResource” in the API name field.
  • Select Data Type as “Record”.
  • Select “AssignedResource” as Object and click Done.
  • Select “ServiceResourceId” from the field drop down
  • Click on “When no records are returned, set specified variables to null.” radio button and click Done.
  • Connect the “Get lead record” action with this get records element.

Using this ServiceResourceId we need to fetch ServiceResource’s name

  • Drag and drop get records element on the flow builder.
  • Enter “Get Service Resource Name” in the label field .
  • Enter “Get_Service_Resource_Name” in the API Name field.
  • Select “Service Resource” as Object.
  • From the Condition Requirements dropdown select All Conditions Are Met (AND).
  • Add condition Id equals {!ServiceResource.ServiceResourceId}.
  • Click “Only the first record” radio button.
  • To store the value of record fetched create a new resource of type “Record” by clicking on the new resource from the Record input field.
  • Select “Variable” as Resource Type.
  • Enter “ServiceResourceName” in the API name field.
  • Select Data Type as “Record”.
  • Select “ServiceResource” as Object and click Done.
  • Select “Name” from the field drop down
  • Click on “When no records are returned, set specified variables to null.” radio button and click Done.
  • Connect the “Get assigned resource” action with this get records element.

Stamp the ServiceResource name on the comments field and meeting URL on the AppointmentBookingInfoURL field

  • Drag and drop update records element on the flow builder.
  • Enter “Update service appointment object” in the label field.
  • Enter “Update_service_appointment_object” in the API Name field.
  • Click “Use the service appointment record that triggered the flow” radio button.
  • Add field “ApptBookingInfoUrl” and MeetingLink(variable that stores the value of meetingURL) as value.
  • Add field “Comments” and ServiceResourceName.Name(variable that stores the value of ServiceResource name) as value and click Done.
  • Connect Update record element with previous get records element.

Create an apex action to send email to the leads email address

Go to developer console and create a new apex class “SendEmailToGuest” and use the code snippet provided below in the class body.
** There are some Out of the box Email templates that Salesforce Scheduler provides and here we are utilising one of them.
** The template unique name used here is specific for appointments scheduled using “Video” AppointmentType and needs to be changed in case of EngagementChannelType.

public class SendEmailToGuest {
   @InvocableMethod(label='Send Email To Guest' callout='true')
   public static Void SendEmailToGuestUsingEmailTemplate(List<inputvariables> inputParams)
       EmailTemplate et = [SELECT Id,Subject, Body FROM EmailTemplate WHERE DeveloperName ='SchedulerUnauthenticatedUserAppointmentTypeEmailTemplateForThirdParty'];
       List<string> toAddress = new List<string>();
       Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
       List<Messaging.SingleEmailMessage> allmsg = new List<Messaging.SingleEmailMessage>();
       try {
       } catch (Exception e) {
    public class inputvariables {
        public String leadEmail;
        public String leadId;
        public String appointmentId;
  • Go to the flow builder again and drag and drop one action element from the left hand side menu.
  • Select “Type” from Filter by drop down.
  • Click on “Apex Action” in the left side menu.
  • Select “Send Email To Guest” in the Action drop down.
  • Enter “Send Email to guest” in the label field and “Send_Email_to_guest” in the API Name .
  • Pass the appointmentId, leadEmail and leadId to the apex action as shown here.
  • In the appointmentId field select {!$Record.Id}.
  • In the leadEmail field select {!leadRecord.Email}.
  • In the leadId field select {!$Record.ParentRecordId}.
  • Click done and connect this apex action with the last element .
  • Now flow is all set, save the flow and activate the flow.
  • Enter “Service Appointment With Zoom” as Flow Label.
  • Enter “Service_Appointment_With_Zoom” as Flow API Name.

This is how the complete flow will look.

Test the flow by creating a service Appointment

  • Schedule a Service Appointment for lead.
  • Appointment Booking URL and Comments field gets populated due to the flow we configured just now.
  • The Unauthenticated user will receive an e-mail .
  • And the Unauthenticated user can join the meeting by clicking the button in the E-mail.

Write this Salesforce Scheduler Appointments to Salesforce Calendar

Go to setup → salesforce scheduler setting and check that event management setting is turned on.
We want that the meeting link for this service appointment is also visible in the description of the associated event.

  • Drag and drop update records element on the flow builder.
  • Enter “Update associated event” in the label field.
  • Enter “Update_associated_event” in the API Name field.
  • Click “Specify conditions to identify records, and set fields individually” radio button.
  • Select “Event” as object from the dropdown.
  • Add a condition “ServiceAppointmentId” equals $Record.Id.
  • Now, Set Field Values for the Event Records and set field as “Description” and value “MeetingLink”(variable that stores zoom meeting link).
  • Click done, connect this update element with the apex action we used for sending mails, save the flow and activate it.
  • Now when you’ll book a service appointment with appointment type “video” the associated event’s description will contain meeting link.

** When you use Einstein Activity Capture to sync events, fields from your Salesforce events are mapped to fields in the connected Microsoft or Google account refer to this document to verify which field is mapped with which.

Appointment is booked using EngagementChannelType with ContactPoint “Video”

There is a very slight difference between the two scenarios

  • Create EngagementChannelTypes with ContactPoint “Video”
  • Create a record-triggered flow as in the previous scenario.
  • From the Condition Requirements dropdown select All Conditions Are Met (AND).
  • Add condition EngagementChannelType Id equals id of the engagement channel type created
  • And use “SchedulerUnauthenticatedUserEngagementChannelEmailTemplateForThirdParty” as the developer name in the apex class used for sending the email to the guest and rest of the steps are common between the two scenarios.

From Chris Albanese: Scheduler – Book multiple appointments using Flow and the LxScheduler namespace


Salesforce Scheduler provides a precision scheduling experience, powered by flow that allows your prospective customers, customers, and employees to schedule appointments with ease and allows you to build stronger relationships with your customers.

Often times, a customer would like to schedule or is required to schedule more than 1 appointment. For example, in one scheduling session, a customer might want to schedule an initial consultation and a follow up appointment 2 weeks later – in other words, schedule the 2 appointments together.

The .gif above depicts scheduling 2 appointments during 1 scheduling session using the customized flow described in this document

Current Situation / Challenge

Product Capability

Scheduler only schedules 1 appointment at a time. Your customer might schedule the first appointment only to find out that the second appointment is not available. Or they might forget to schedule the second appointment.

Complexity of Custom Development

The good news is that Salesforce Scheduler has a robust API available, allowing customers to create their own scheduling experiences to meet their needs. The not so good news in this is that if you use the API to find available appointments, you need to build your own screen to display those to the user. In other words, the Time Slot Selection screen that comes with Scheduler cannot be customized.

The screen above cannot be customized. For example, you cannot allow a user to select 2 time slots.

A Lower Complexity Custom Solution

You can build a custom scheduling solution which can provide options to select two timeslots by leveraging a few things which are mentioned below.

And this solution really has a basic UI. In its current form it is best suited for an internal user. For external use, you should extend the solution to provide more natural calendar controls (like the out of the box timeslot selection component that Scheduler provides). That, of course, will require more complexity, but certainly worth if to deliver a superior user experience.

Scheduler LX namespace / Apex Methods

Use the Scheduler LX namespace to easily get available time slots. For example, call the GetAppointmentCandidates method to get 2 lists of available times slots. Create an @invocableMethod that will allow you to call this from a Flow.

Flow Datagrid Pack

Use this package to display the available time slots from the API call and allow the user to select these. This package can be found on the appexchange.


Use the power and flexibility of Flow to connect both of these together as well as leverage existing Scheduler flow screen components such as Select Territory to create a custom scheduling experience.

Installation / Technical Details

Using the above mentioned custom options, here is a version of the custom solution that we built to attain the experience we discussed.

You can Install this package in your sandbox and check it out for yourselves. Use it to jump start your own custom scheduling solution for booking more than 1 appointment at the same time.

Create an Account Action (or Opportunity, Lead or Case) to launch the flow called LX Book 2 Appointments that is included in this package. Package link:

Github Repository (does not include Flow Datagrid Pack):


AppExchange Package

  • Flow Datagrid Pack – this solution uses the DataGrid Pack – Tiles screen component to provide a user interface for selecting a time slot. The package link above contains the Tiles component already and does not need to be installed from the appexchange.

Apex Classes

  • LXGetSlots – a class with an @invocableMethod. This class calls the getAppointmentCandidates API to fetch available time slots. This class will format the results so they can be rendered in the DataGrid Pack – Tiles component.
  • schedulerCandidates – represents a time slot returned from the API
  • LXGetSlotsTest, TestUtility – Test class and test class helper


  • LX Book 2 Appointments – this flow is set up to allow booking of 2 appointments
  • LX Get Slots Helper Flow – this flow is called by LX Book 2 Appointments. It calls the LXGetSlots apex class to get time slots
  • LX Service Appointment Helper flow – this flow is called by LX Book 2 Appointments. Its purpose is to create a service appointment for a selected time slot. It uses the Scheduler Save action and it has a formula field to construct the JSON needed for the Save action

LX Book 2 Appointments flow – calling the API

LX Book 2 Appointments flow – creating 2 appointments

Customize it!

If you want to book 3 or more appointments instead of 2, add additional steps to the LX Book 2 Appointments Flow.

  • Any variable that ends with a ‘2’ should be replicated and end with a ‘3’
  • Formula fields that you replicate should also have 2’s replaced with 3’s
  • Replicate the pattern you see in the flow to find slots for the 3rd appointment
  • Add a Datagrid Pack – Tiles screen component for the 3rd appointment to the Select Slots screen
  • Replicate the pattern you see in the flow for the saving of the appointment

  • Replicate the LX Get Slots Helper Flow 2 step. Note the pattern to determine if slots were found and replicate those
  • Replicate the LX Service Appointment Helper Flow 2 step and related steps. Note the pattern to determine if slots were found and replicate those
  • When replicating a step, make sure to update any input/output variables from ‘2’ to ‘3’.
  • Add Datagrid Pack – Tiles component for the 3rd appointment, make sure to update any input/output variables from ‘2’ to ‘3’.

From Faith Kindle: Generating an ICS file after Service Appointment Save in Salesforce Scheduler flows


Salesforce Scheduler provides tools needed to simplify appointment scheduling in Salesforce. With Salesforce Scheduler, it’s easy to embed appointment scheduling into standard Salesforce workflows, such as leads and referrals, opportunities, and accounts.

The outcome of the scheduling process is creation of a Service Appointment record which has the details of the appointment created by the Scheduler engine and informed by input from the end user. Salesforce Scheduler also includes out-of-the-box functionality that will generate a related event on the user’s (Service Resource’s) Salesforce calendar when ‘Event Management’ is enabled in Setup Salesforce Scheduler. Settings. Tools like Einstein Activity Capture can extend Scheduler’s “Event Management” functionality by synching these events to user’s calendar applications like Outlook, Google calendar, etc.

Problem Statement

While Einstein activity capture may be helpful for users using Salesforce, the question most implementation experts ask is – “How can we help prospects and customers scheduling externally to download the appointment confirmation to their personal calendars?”

An example can be with a new prospect interested to meet a financial advisor of a bank going ahead and scheduling an appointment using a contact us page from a company website (where the implementation done might be a guest flow exposed out on the company’s website) wants to download the appointment confirmation to their google calendar.

How do we solve for it?
The answer is with providing a ICS file – after the creation of a service appointment to download.

What is an ICS file?

Internet Engineering Task Force defined a standard in 1998 for sharing calendar events. An ICS file is a calendar file saved in a universal calendar format used by several email and calendar programs, including Microsoft Outlook, Google Calendar, etc. It allows users to share calendar information on the web and over email. Because all popular calendar applications can use ICS files to add event details to a calendar, ICS files are the most common type of files used to exchange calendar information.

ICS files are plain text files that contain information about a calendar event, including the event’s

  • Title
  • Summary and description
  • Start and end times
  • Location
  • Alert trigger

Creating ICS file in Salesforce

While we understand what goes into an ICS file, let’s look at some sample process to create one in Salesforce.

Making an ICS file available to download for Google Calendar, Microsoft Outlook, and Office 365

While there are several ways to make the ICS file available for download, we can make it EASY by making it available for download via link when (or after) we show the Appointment confirmation. In this example we will use flow to generate an ICS URL that, when clicked, will translate to an ICS file for Google Calendar, Microsoft Outlook, and Office 365. (Note: this is just one of many possible approaches. This approach can be be used in conjunction with Salesforce Scheduler’s out-of-the-box Inbound or Outbound new appointment flows, or your custom cloned versions of these flows.)

  • Create custom fields on the Service Appointment object with datatype as URL
    • In our example we created three URL fields with API names Google_ICS__c, Outlook_ICS__c, and Office_365_ICS__c
    • Note: this step is NOT necessary for displaying the URLs on your confirmation page, however may be useful later if, for example, you are sending a confirmation email to your customers and want to include the ability to download the appointment within that email.
  • On the Flow used for appointment booking
    • add Get Record action to retrieve the Service Appointment record after the Save Appointment action is executed
  • with a filter condition to retrieve the Service Appointment created in this flow
  • Add the following Formula variables to your flow. Note the reason for these different formulas is that each format we generate will expect slightly different syntax – for example, Google does not want hyphens or colons in its dates, and Microsoft uses “+” instead of spaces.
    • Note: you can modify these formulas to your business’s preferences, if they do not specifically match. For example, you may not want your subject to be “Service Appointment Subject with Service Resource first name.” In that case, you can remove the “with” and Service Resource first name from the formula to leave just the Service Appointment Subject, for example. Or, you are using a virtual web conference as your meeting place. In that case, you may want to input the web conference link in your description, or modify the text between the URLs’ location parameters in order to set the web conference as your location for your downloadable events.
    • StartDateFormatted
    • EndDateFormatted
    • GoogleICSURL; & {!Get_SA.Subject} & ” with ” & {!Get_SA.Owner:User.FirstName} & “&details=” & {!Get_SA.Description} & “&location=” & {!Get_SA.Street} & “, ” & {!Get_SA.City} & “, ” & {!Get_SA.State} & “, ” & {!Get_SA.PostalCode} & “&dates=” & {!StartDateFormatted}&”/”&{!EndDateFormatted}
    • OutlookICSURL; & {!Get_SA.Subject} & “+with+” & {!Get_SA.Owner:User.FirstName} & “&body=” & Substitute({!Get_SA.Description}, ” “, “+”) & “&location=” & Substitute({!Get_SA.Street}, ” “, “+”) & “+” & Substitute({!Get_SA.City}, ” “, “+”) & “+” & Substitute({!Get_SA.State}, ” “, “+”) & “+” & Substitute({!ServiceAppointment.PostalCode}, ” “, “+”) & “&startdt=” & Substitute(TEXT({!Get_SA.SchedStartTime}), ” “, “T”)& “&enddt=” & Substitute(TEXT({!Get_SA.SchedEndTime}), ” “, “T”)& “&allday=” & “false”
    • Office365URL; & {!Get_SA.Subject} & “+with+” & {!Get_SA.Owner:User.FirstName} & “&body=” & Substitute({!Get_SA.Description}, ” “, “+”) & “&location=” & Substitute({!Get_SA.Street}, ” “, “+”) & “+” & Substitute({!Get_SA.City}, ” “, “+”) & “+” & Substitute({!Get_SA.State}, ” “, “+”) & “+” & Substitute({!Get_SA.PostalCode}, ” “, “+”) & “&startdt=” & Substitute(TEXT({!Get_SA.SchedStartTime}), ” “, “T”)& “&enddt=” & Substitute(TEXT({!Get_SA.SchedEndTime}), ” “, “T”)& “&allday=” & “false”
  • Add an Updates Records component to update your Service Appointment with the URLs you generated. Note: like the custom fields storing the URLs we generate, this step is NOT necessary for displaying the URLs on your confirmation page, however may be useful later if, for example, you are sending a confirmation email to your customers and want to include the ability to download the appointment within that email.
  • Display the URL fields on the confirmation page by dragging a Display Text component under the out-of-the-box Service Appointment Confirmation.
    • In the example below, we include a generic “Add to Calendar” graphic and hyperlinked text “Google | Outlook | Office 365.”
    • We use the hyperlink to link Google text to variable {!GoogleICSURL}
    • Similarly, Outlook text is linked to variable {!OutlookICSURL}
    • And Office 365 text is linked to variable {!Office365ICSURL}


Salesforce Scheduler Customization: Adding an ICS URL for a Clickable Link to Download Appointment


From Sunil Nandipati: Deploying Salesforce Scheduler from Sandbox to Production Environments


Customers who have streamlined development processes typically use multiple non-production environments (one for each probably – development, QA, integration, data migration, SIT and UAT). And it is a general practice that development teams develop in development sandboxes or scratch orgs first, check in their code to a repository and add instructions to deployment trackers – for deployment teams to perform the deployment steps. These can include any manual steps – related to enabling features, adding data etc. Typically anything that cannot be done using the metadata API.


It is NOT a good practice to expect the deployment teams to repeat these steps manually in every single Org. Reasons can be many. To quote a few – prone to human errors while executing manual steps, time consuming, too much detailed documentation will be required and some customers do NOT prefer a partner or consultant to have administrator access to environments other than development.

Guidelines while deploying Scheduler

While deploying Salesforce scheduler between environments – consider categorizing your components into the following areas

Pre deployment activities

Include steps that need to be performed before migrating the codebase using the Metadata API. Ensure the system administrator has the Scheduler Permission Set license assigned first, then consider any manual steps of the features that are NOT yet supported by the MetaData API (or) any dependent packages that are required for your implementation.

Deploy code using Metadata API

Step 1 – Include feature settings that are related to “Salesforce Scheduler Settings” which need to be either Enabled or Disabled, should be covered first. Refer the Industries settings here –
Note: The settings mentioned in Italic and which do NOT have a Metadata Reference are currently NOT supported by Metadata API – will be addressed in future releases (Safe harbor). If you are using these features – activate them manually as part of your pre-deployment steps

Salesforce Scheduler SettingsMetadata Reference
Event ManagementenableEventManagementOrgPref
Block Resource AvailabilityenableBlockResourceAvailabilityOrgPref
Multi Attendee EventenableCreateMultiAttendeeEventOrgPref
Appointment DistributionappointmentDistributionOrgPref
Aggregate Resource UsecaptureResourceUtilizationOrgPref
Publish Appointments as Platform EventsenableEventWriteOrgPref
Multi Resource SchedulingenableMultiResourceOrgPref
Concurrent SchedulingenableOverbookingOrgPref
Operating Hours for Service Territory Members for Work Type GroupsenableTopicTimeSlot
Salesforce Scheduler for Health Cloud
Resource Appointment SharingenableShareSaWithArOrgPref
Multiple Topics For ShiftsenableMultipleTopicsForShiftsOrgPref
Schedule Appointments Using Engagement ChannelsenableAppFrmAnywhereOrgPref
Main Service ResourceenableAnyResourceTypeOrgPref
Drop In AppointmentsenableDropInAppointmentsOrgPref
Drop In Skill and Skill Level MatchingenableDropInSkillMatchingOrgPref
Resource Availability Sharing Using Actionable ListenableActionableListOrgPref

Step 2 – Include any customizations done to the product offering should be added to the Codebase (repository). Consider evaluating your customizations around

  1. Sharing settings – OWD and Sharing rules (include groups or roles – if sharing rules are around those)
  2. Custom fields on Scheduler objects
  3. New Picklist values on Scheduler objects
  4. Custom buttons, actions and links
  5. Changes to page layouts
  6. Flows
  7. Custom components that are added to the flows
  8. Custom classes and triggers
  9. Validation Rules
  10. Experience Cloud
  11. Lightning Out for Guest user on a customer website (endpoint URLs will be different)

Post deployment activities

Include steps that need to be performed for the scheduler solution to work. This will mostly include migrating your data and setting up the users and adding scheduler PSLs. Consider leveraging data loader or an ETL tool for data migrations and ensure data integrity (order of loads) and references are maintained post migration between the records in multiple objects.

Salesforce Foundation Objects
SObjectNameLoad SequenceRelationships to Establish
User2Profile, UserRole
PermissionSetAssignment3PermissionSet Or PermissionSetGroup, User
Salesforce Scheduler CORE Objects
SObjectNameLoad SequenceRelationships to Establish
Skill1Can also be migrated as Metadata (But recommendation is to do it as data migration)
Skill Requirement2Skill
Operating Hours1
Operating Hours Holiday2
Work Type Group1
Service Territory2Operating Hours
Work Type2Operating Hours
Work Type Group Member3Work Type Group, Work Type
Service Territory Work Type3Service Territory, Work Type
Service Resource3UserIf using Assets as Service Resource, the order should be after Assets
Service Resource Skill4Service Resource, Skill
Time Slot4Operating Hours, Work Type Group
Service Territory Member4Operating Hours, Service Territory, Service Resource
Appointment Assignment Policy1
Appointment Scheduling Policy2Appointment Assignment Policy
Appointment Topic Time Slot5Work Type Group, Work Type, Time Slot
Salesforce CORE Transactional Objects
SObjectNameLoad SequenceRelationships to Establish
Account3UserOwner of the Account
Parent Account Id should be an additional Step if you manage hierarchy of Accounts
And probably any custom look-ups if you have included Custom Fields
Contact4Account, User
Opportunity5Account, Contact, User
Lead6Account, Contact, Opportunity, UserWhen migrating Converted Leads
Asset5Product, Account, Contact
Salesforce Scheduler Transactional Objects
SObjectNameLoad SequenceRelationships to Establish
Resource Absence4Service Resource
Resource Preference6Service Resource, Related (Account, Lead OR Opportunity)
Shift6Service Resource, Work Type, Appointment Topic TimeSlot
Service Appointment7Service Territory, WorkType, Account, Parent Record (Either Account, Lead OR Opportunity)
Assigned Resource8Service Resource, Service Appointment
Event9Service Appointment, WhatId (Asset, Account OR Opportunity), Owner (Users), WhoID (Lead OR Contact)
Appointment Invitation8Work Type OR Work Type Group, Service Territory
Appointment Invitee9Appointment Invitation, Service Resource

Note – even the scheduling policies and assignment policies have objects (AppointmentSchedulingPolicy and AppointmentAssignmentPolicy) – they can be migrated.

Few best practices to keep in mind

  1. Consider deactivating picklist values that come OOTB if there is NO plan to use them in your solution
  2. If you have hardcoded any IDs in your flows (for defaulting any records like Work Type Group, service territory etc.) – replace with Production IDs wherever appropriate
  3. Turn OFF Rules (Validation, Escalation, Assignment, Workflows, Process builders, Flows, Sharing Rules) to avoid getting errors while performing data loads
  4. Re-calculate Sharing Rules post migration of all data
  5. And Finally explore sfdx-cli data tree import option to load in fewer amount to steps
    1. Make sure the number of records getting loaded are fewer than 200 in a single step (thats the limitation to look for when using the data tree import option with sfdx-cli)
    2. Refer here –


From Sunil Nandipati: Scheduling a Contact

Problem Statement

With Salesforce Scheduler, it’s easy to embed appointment scheduling into standard Salesforce workflows, such as leads and referrals, opportunities, accounts (and person accounts in the B2C Model) and ALSO cases (as of Winter ’23 release).

But NOT for Contacts (with B2B model).

In this blog, we will look at options to schedule a contact.

Note: Field Service Lightning Product extends this to additional objects – Work Order, Work Order Line Item and Asset.

Understand Scheduler OOTB Behavior

Before we start looking at an option how we can customize the experience for scheduling around a contact, lets try to understand the core objects we have from Salesforce Scheduler that capture a booked appointment using the OOTB behavior and how we can reuse the existing OOTB available references.

  • Service Appointment
  • Assigned Resources
  • Event

We will keep our focus understanding the relationships and the references

ObjectFieldData TypeUsage
Service AppointmentParentRecordLookup(Account, Opportunity, Lead, Case)Using Salesforce Scheduler we can create a Service Appointment only around Account, Opportunity, Lead or Case.
Service AppointmentAccountLookup(Account)Defaults to the Customer account’s for inbound, Left blank for a Lead, Case and related account if booking around an opportunity.
Service AppointmentContactLookup(Contact)Defaults to the user’s contact record when requesting from a customer community via an inbound authenticated user booking scenario. Left blank otherwise.

We will use this standard field to Stamp the Contact ID – during Service Appointment creation when doing an Outbound Scheduling from Internal Salesforce in this blog.
Assigned ResourceServiceAppointmentMaster-Detail(Service Appointment)Service Appointment object does NOT hold the service resource information, it is primarly stored in the Assigned Resource object. This is the reference to Service Appointment.
Assigned ResourceServiceResourceLookup(Service Resource)Service Resource reference (can be either a user or an asset)
Service ResourceRelatedRecordLookup(User)Service resource object reference to a User – which can be either a Platform Starter / Partner Community / Customer Community Plus / Salesforce license type user
EventWhoIdLookup(Contact,Lead)Events are like your calendar events. The person who will attend this event is captured here.
EventWhatId (Related To)Except Lead and Contact, Other objectsAnd this event is related to what in Salesforce
EventServiceAppointmentLookup(Service Appointment)When Event Management is turned ON on the Scheduler settings, every time a service appointment is created, it creates a related Event – this is to hold that references
EventOwner (Assigned To)Lookup(User,Calendar)Represents the owner of the record (internal user or an external calendar)

Customization Guidance to schedule a Contact

Having looked at the above objects and references, it is pretty much clear that we CAN reuse the existing Contact lookup on Service Appointment. When using Events, we WILL have to make sure the related EVENT gets created with this Contact reference as well.

To achieve this we need to modify the existing template slightly. Try these steps outlined below

  1. Start with a cloned flow from the template (In this example we have cloned the “Outbound New Appointment” template to create a flow called “Outbound New Appointment for Contact”)
  2. Since we plan to launch this from a Contact record, the recordId will be the Contact.Id that will be passed to the Flow. So we need to make sure we capture the Contact.AccountId as well. Create a variable to capture the AccountId. Service Appointment ParentID field can only accept records from Lead, Account and Opportunity, so lets make sure we pass the account information from the Contact
  • Assignment – Set Initial Values :: make sure the recordId is assigned to ServiceAppointment.ContactId
  • Screen – Attendees Screen :: ensure the Contact ID assignment is NOT empty – change it to {!ServiceAppointment.ContactId}
  • Screen – Review Screen :: ensure the Contact ID and Parent Record ID assignments are correct
  • Finally make sure the Event Management is turned ON
  1. Save the Flow and Activate it
  2. Add a Lightning Action to Launch this flow
  • Add this Lighting Action to your Contact Page Layout
  • Finally, test it for yourself. Here is a demo of the same configuration tested on a developer trial org

Considerations when using with Field Service

As mentioned in the preface – Scheduling an appointment gets extended to additional Objects which are NOT supported by Salesforce Scheduler when using Salesforce Field Service product (Earlier Field Service Lightning).

Some considerations to keep in mind when using both products –

Especially with both the products (Scheduler and Field Service) in the same org, ensure you take extra care around Event Management using a custom route rather than turning ON the Event Management option as mentioned above.

From Chris Albanese and Chris Hartt – Scheduling 1 appointment with multiple work types


What if your business offers appointment booking for multiple services with a single appointment. For example, you offer auto services and allow customers to select from a menu of options, such as oil change, tire rotation, brake service and others. The customer may select 1 service or multiple services, and depending on the services selected, the duration of the appointment should be long enough enough to accommodate the selected services.


Above: Example of a flow screen prompting a user to select 1 or more auto services

What if you also had many store locations, and each store might offer a different menu of options. For example, the Wakefield store offers all services but the Reading store does not offer brake services.

With Salesforce Scheduler, you can accommodate this out of the box by creating many Work Type Groups to represent the combinations of services.

Consider the following table of Work Type Groups with its limitless number of combinations

Oil Change30
Tire Rotation30
Wiper Service15
Oil Change and Brakes90
Brakes and Tire Rotation90
Tire Rotation and Wiper Service45
Oil Change and Wiper Service45
Brakes and Wiper Blades75
…and many more combinations

But, this is probably not the most practical way to offer a menu of services. This document describes an alternative approach which includes 2 custom fields and a small bit of Apex code. Check out the details below.

Solution Overview

This solution allows you to run the Scheduler flows with a few minor configurations and an Apex @invocableMethod, allowing the user to select the desired services and perform precision scheduling without all of the permutations of work type groups.

The solution consists of custom fields on the Work Type object and Service Appointment object, an Apex Class, Work Type Group and Work Type data organized in a specific way and a configured flow. The flow makes use of the FilterByResourceIds parameter to ensure only those resources who have the skills needed are returned.


Work Type Groups

Create Work Type Groups only to reflect the duration of the services required. For example, Work Type Groups called 30 Minutes, 45 Minutes, 60 Minutes, 90 Minutes, etc. These are tied to Work Types with the respective durations.


Create Skills for each service required. For example, create an Oil Change Skill, a Brake Service Skill, a Tire Rotation Skill and so on.

Service Resources and Service Territories

Create Service Territories to represent the store locations and create 1 resource for each service lane or bay present in the store. Assign skills to service resources to define the services offered. For example, if the store offers oil changes and brake service, but no other services, then assign only those 2 skills to the service resources associated with the store.


Service Appointment

Add a custom text (255) field called Selected Services. This will store the id’s of the work types that were selected by the user. This can be used by a rescheduling flow (not described in this document).

Work Types

Add a custom picklist field called Service Type, with values of Service and Scheduling.

Create 2 types of Work Types:

Service Work Types

These are work types that represent each service, the skill required and the expected duration. For example,

WT Name = Oil Change, Skill Required = Oil Change, Duration = 30 Minutes, Service Type = Service

WT Name = Brakes, Skill Required = Brake Service, Duration = 60 Minutes, Service Type = Service

These work types are not assigned to any work types groups or service territory work types.

Scheduling Work Types

These are work types that represent a total duration, have no skills required and the expected duration. For example,

WT Name = 30 Minute Service, Duration = 30 Minutes, Service Type = Scheduling

WT Name = 60 Minute Service, Duration = 60 Minutes, Service Type = Scheduling

These work types are assigned to Work Type Groups (see screenshot below) and they are assigned to service territory work types.


Pic above: Work types for Service (with Skills required) and work types for Scheduling (no Skills required)


Pic Above: Work Type Groups just for Scheduling

Apex Class

An Apex class is used to determine the scheduling Work Type Group which is just large enough to cover the list of input Services. It accepts a list of Work Type ids and returns a Work Type Group id and a text field containing a comma separated list of Service Resource ids who have the skills required for the input Services.

The code for the apex class and a test class is contained in the package file at the end of this document.


Modify the Inbound New Guest Appointment flow to prompt for the services required (service work types), call the apex class and then present the list of time slots available.


Pic Above: Flow with new steps added to prompt for services and call the apex class.


Pic Above: New Screen which prompts for services. Select services is a picklist tied to a record choice set which selects only Work Types where Service Type = Service.


Call to Apex which returns the applicable work type group id with the duration large enough to accommodate the selected services. Also set the FilterByResourceIds field, which will be used in the select location and select time slots screens.


Video of it in action

Check out the short video below.


Try it yourself in your own sandbox. The custom fields, Apex Class, Test Classes and an example of a configured Inbound New Guest Appointment flow are included in this repo.

Github Repository


FilterbyResourceIds limitation

Since FilterbyResourceIds is limited to 50 service resource ids, you should ensure that you pass in a service territory id to the Apex Method so as not to exceed this limit.

Modify line 24 of the Apex code to select the specific service territory selected by the user.

listST = [select id from serviceterritory where isactive = true];

Lines 50-56 contain code to limit the total number of service resource ids to a maximum of 50. If the limit of 50 changes in a future release, you should change this code to reflect the new limit.

Rescheduling Flow

A rescheduling flow is not included here. If rescheduling is part of your use case, use the Service Appointment custom field created in this package to retrieve the services selected by the user when the appointment was scheduled. Add the custom screen and call to the Apex method to retrieve the corresponding scheduling Work Type Group, similar to how the flow in the package has been configured.