Automations for Sales Cadences in High Velocity Sales | Salesforce Developers Blog

High Velocity Sales (HVS) is a Sales Cloud solution to speed up the sales process for inside sales teams. The basic setup of HVS is normally carried out by an admin. However, developers are often needed to implement more advanced automations. HVS includes several features, such as Sales Cadences, Einstein Lead Scoring, Email Integration, and Sales Dialer. In this blog post, we’ll cover what developers need to know about Sales Cadences, and we’ll also deep dive into how to implement common automations, with and without code.

Before starting: if you don’t know the basics about HVS, watch this 2 minute demo.

Automating inside sales workflows with Sales Cadences

A Sales Cadence is a representation of an inside sales workflow; it guides sales reps through the prospecting and opportunity nurturing processes of a sale. The steps for a sales cadence are configured with clicks in Sales Cadence Builder. For instance, this sales cadence contains a combination of emails and calls that a sales rep should make to reach out to a possible customer. We’ll use this sales cadence for this blog post examples.

Prospects can be assigned to sales cadences. This can be done manually, or in an automated way, by calling the assignTargetToSalesCadence invocable action. Once this is done, the prospect enters into the sales cadence, and the next steps to perform become visible to the appropriate sales rep.

Sales Cadences data model

Under the hood, the definition of a sales cadence is stored as data in a set of new sObjects:

  • ActionCadence: created when you use the High Velocity Sales app to create a sales cadence
  • ActionCadenceStep: represents a step; if the step is a branch step, then corresponding ActionCadenceRule and ActionCadenceRuleCondition records are also created
  • ActionCadenceRule: represents the logic that a branch step uses to make decisions in your sales cadence
  • ActionCadenceRuleCondition: represents the logic for a branch step. While ActionCadenceRule represents the full logical expression, ActionCadenceRuleCondition represents each component of the expression. So if the logical expression is (A = 2) AND (B = 4), then the rule conditions would be (A=3), (B=4) and the rule is the combination of them with the AND operator.

There are two additional sObjects that keep track of the sales cadence step that a prospect is in:

  • ActionCadenceTracker: created when you assign a prospect to a sales cadence
  • ActionCadenceStepTracker: created each time the prospect moves to a new step

Those sObjects are available through the SOAP API — which means that you can also query them. At the moment, the create() operation is not available for sales cadence objects, however this is on the product roadmap and will be possible soon. This means that you can’t programmatically create them using Apex or APIs. You can only create them from Sales Cadence Builder.

To better understand the data model, let’s take a look at what happens when you add a lead to the simple email sales cadence that we saw above.

When this is done, ActionCadenceTracker and ActionCadenceStepTracker records are created. Then we can retrieve the ActionCadenceTracker record created by executing:

SELECT ActionCadence.Name, Target.Name, CurrentStep.StepTitle, LastCompletedStep.StepTitle, State, RelatedToId FROM ActionCadenceTracker

This is what you’ll obtain:

ActionCadence.Name Target.Name CurrentStep.StepTitle LastCompletedStep.Title State
Simple Email Cadence Robert Janzen Intro Email Running

Similarly, we can take a look at the ActionCadenceStepTracker record that has been created:

SELECT ActionCadenceName, Target.Name, StepTitle, DueDateTime, IsActionTaken, ActionTakenDatetime, State from ActionCadenceStepTracker

ActionCadenceName Target.Name StepTitle DueDateTime IsActionTaken IsActionTakenDateTime State
Simple Email Cadence Robert Janzen Intro Email 2021-08-31T21:59:59.000+0000 FALSE Active

Calling HVS invocable actions

Basic automations can be implemented using clicks. HVS has the following list of invocable actions that you can invoke from places, such as Flow, Process Builder, the REST API (as we do in this example), or Apex (as we do here), to implement your automations:

  • assignTargetToSalesCadence
  • removeTargetFromSalesCadence
  • pauseSalesCadenceTracker
  • resumeSalesCadenceTracker
  • changeSalesCadenceTargetAssignee
  • modifyCadenceTrackerAttributes
  • sendSalesCadenceEvent

Note that calling invocable actions from Apex can be simplified using this developer preview feature in Winter ‘22.

Take a look at a more detailed definition of the available actions in the HVS Invocable Actions guide.

Working with Action Cadence Tracker changes

As mentioned earlier, for more advanced automations, you’ll need to write code. A common automation use case is to be able to react to ActionCadenceTracker changes to execute an action based on certain conditions. For instance, change the lead status to “Working” when the ActionCadenceTracker is first created or to “‘Closed” when it gets completed. One limitation that exists as of today is that you can not create triggers for ActionCadenceTracker and ActionCadenceStepTracker. However, Change Data Capture (CDC) can be used to build automation. First, you’ll have to activate CDC for the ActionCadenceTracker object:


Then, you can listen to CDC events in Apex, creating an Async Apex Trigger. For instance, let’s implement an automation that changes a lead status to “Working” when it is added to a sales cadence:

trigger UpdateLeadWhenAddedToCadenceTrigger on ActionCadenceTrackerChangeEvent (after insert) {

    Set<Id> leadIds = new Set<Id>();
    for (ActionCadenceTrackerChangeEvent event : Trigger.New) {
        EventBus.ChangeEventHeader header = event.ChangeEventHeader;
        // If tracker created (lead added to Sales Cadence), change Lead Status to 'Working'
        if (header.changeType == 'CREATE') {
            // The Id may not be a leadId, but in that case it won't be returned by the query
            leadIds.add(event.TargetId);
        }
    }
    
    List<Lead> leadsToUpdate = [SELECT Status FROM Lead WHERE Id IN :leadIds];
    for (Lead leadToUpdate : leadsToUpdate) {
        if (leadToUpdate.Status != 'Working') {
            leadToUpdate.Status = 'Working';
        }
    }
    
    update leadsToUpdate;
}

Unfortunately, listening to CDC events in Flow or Process Builder is not possible. You’ll have to create an Async Apex Trigger that transforms the CDC event into a custom platform event, and then listen to the custom platform event.

For instance, let’s say the custom platform event is called SalesCadenceTrackerEvent__e. This is how you would implement the CDC trigger:

/** 
 * This trigger listens for change events on the ActionCadenceTracker and publishes a platform
 * event (SalesCadenceTrackerEvent__e) whenever a tracker is first started and when it is
 * completed.  You can then use Flows or Process Builder to listen to the platform event. */
trigger ActionCadenceTrackerChangeEventTrigger on ActionCadenceTrackerChangeEvent (after insert) {
  List<SalesCadenceTrackerEvent__e> cadenceEvents = new List<SalesCadenceTrackerEvent__e>();
  List<String> trackerIdsToQuery = new List<String>();
  for (ActionCadenceTrackerChangeEvent event : Trigger.New) {
      EventBus.ChangeEventHeader header = event.ChangeEventHeader;
      List<String> recordIds = event.ChangeEventHeader.getRecordIds();
      String trackerId = recordIds.get(0);
      // If Tracker was completed, then we should send an event
      if (header.changeType == 'UPDATE') {
          if (event.State == 'Complete') {
              // Update events do not have all the info we need so we have to query
              trackerIdsToQuery.add(trackerId);
          }
      }
      // If Tracker was created, then we should send an event
      if (header.changeType == 'CREATE') {
          SalesCadenceTrackerEvent__e cadenceEvent = new SalesCadenceTrackerEvent__e();
          cadenceEvent.TrackerId__c = trackerId;
          cadenceEvent.CadenceId__c = event.ActionCadenceId;
          cadenceEvent.State__c = event.State; // This will be 'Running'
          cadenceEvent.TargetId__c = event.TargetId; // This will be the lead, contact, person account id
          cadenceEvents.add(cadenceEvent);
      }
      // Query for the trackers info for UPDATES
      if (trackerIdsToQuery.size() > 0) {
          List<ActionCadenceTracker> trackers = [SELECT Id, ActionCadenceId, State, TargetId from ActionCadenceTracker WHERE Id in :trackerIdsToQuery ];
          for (ActionCadenceTracker tracker : trackers) {
              SalesCadenceTrackerEvent__e cadenceEvent = new SalesCadenceTrackerEvent__e();
              cadenceEvent.TrackerId__c = tracker.Id;
              cadenceEvent.CadenceId__c = tracker.ActionCadenceId;
              cadenceEvent.State__c = tracker.State; // This will be 'Complete'
              cadenceEvent.TargetId__c = tracker.TargetId; // This will be the lead, contact, person account id
              cadenceEvents.add(cadenceEvent);
          }
      }
  }
  // Publish the events for Flow or Process Builder to listen to
  EventBus.publish(cadenceEvents);
}

Note that the code above only throws the event when the cadence tracker is first created or when it gets completed (state = Complete). Then, those newly created SalesCadenceTrackerEvent__e can be detected from Flow or Process Builder to implement the desired automations.

Here, you have a diagram that represents one of the possible flows in this automation:


If you want to know more, take a look at this document that contains detailed instructions on how to implement this automation.

Working with ActionCadenceStepTracker changes

Similarly, if you want to react to ActionCadenceStepTracker changes, you’ll have to use CDC. You can then listen to CDC events from a trigger and implement the required automations with code (as we do in this example). Alternatively, you can convert the CDC event to a custom platform event, same as we did with Action Cadence Tracker. Here you have the code to do so:

/**
 * This trigger listens for change events on the ActionCadenceStepTracker and publishes a platform
 * event (SalesCadenceStepTrackerEvent__e) whenever a step tracker is first started and when it is
 * completed.  You can then use Flows or Process Builder to listen to the platform event. */
 trigger ActionCadenceStepTrackerChangeEventTrigger on ActionCadenceStepTrackerChangeEvent (after insert) {
    List<SalesCadenceStepTrackerEvent__e> cadenceEvents = new List<SalesCadenceStepTrackerEvent__e>();
    List<String> stepTrackerIdsToQuery = new List<String>();
    for (ActionCadenceStepTrackerChangeEvent event : Trigger.New) {
        EventBus.ChangeEventHeader header = event.ChangeEventHeader;
        List<String> recordIds = event.ChangeEventHeader.getRecordIds();
        String stepTrackerId = recordIds.get(0);
        // If StepTracker was completed, then we should send an event
        if (header.changeType == 'UPDATE') {
            if (event.State == 'Completed') {
                // Update events do not have all the info we need so we have to query 
                stepTrackerIdsToQuery.add(stepTrackerId);
            }
        }
        // If StepTracker was created, then we should send an event
        if (header.changeType == 'CREATE') {
            SalesCadenceStepTrackerEvent__e cadenceEvent = new SalesCadenceStepTrackerEvent__e();
            cadenceEvent.StepTrackerId__c = stepTrackerId;
            cadenceEvent.CadenceId__c = event.ActionCadenceId;
            cadenceEvent.CadenceStepId__c = event.ActionCadenceStepId;
            cadenceEvent.State__c = event.State; // This will be 'Active' 
            cadenceEvent.StepType__c = event.StepType; // This will be SendAnEmail, MakeACall, etc.
            cadenceEvent.TargetId__c = event.TargetId; // This will be the lead, contact, person account id
            cadenceEvent.StepTitle__c = event.StepTitle;
            cadenceEvents.add(cadenceEvent);
        }
    }
    // Query for the step trackers
    if (stepTrackerIdsToQuery.size() > 0) {
        List<ActionCadenceStepTracker> stepTrackers = [SELECT Id, ActionCadenceId, ActionCadenceStepId, State, StepTitle, StepType, TargetId from ActionCadenceStepTracker WHERE Id in :stepTrackerIdsToQuery ];
        for (ActionCadenceStepTracker stepTracker : stepTrackers) {
            SalesCadenceStepTrackerEvent__e cadenceEvent = new SalesCadenceStepTrackerEvent__e();
            cadenceEvent.StepTrackerId__c = stepTracker.Id;
            cadenceEvent.CadenceId__c = stepTracker.ActionCadenceId;
            cadenceEvent.CadenceStepId__c =  stepTracker.ActionCadenceStepId;
            cadenceEvent.State__c =  stepTracker.State;// This will be 'Completed'
            cadenceEvent.StepType__c = stepTracker.StepType; // This will be SendAnEmail, MakeACall, etc.
            cadenceEvent.TargetId__c = stepTracker.TargetId; // This will be the lead, contact, person account id
            cadenceEvent.StepTitle__c = stepTracker.StepTitle;
            cadenceEvents.add(cadenceEvent);
        }
    }
    // Publish the events for Flow or Process Builder to listen to   
    EventBus.publish(cadenceEvents);
}

Note that the code above only throws the event when the cadence step tracker is first created (state = Active) or when it gets completed (state = Completed).

Here you have another diagram that represents one of the possible flows in this automation:

Take a look at this other document to read detailed instructions on implementing this automation.

Next steps

In this blog post, we’ve covered the most important points to successfully implement automations for Sales Cadences in High Velocity Sales.

If you want to check more examples, take a look at this documentation page in which some more automation ideas are explained. We’ve also uploaded all the code examples to this GitHub repo, for an easier consumption.

Finally, I recommend tackling this Trailhead trail, in which you’ll be able to learn the most important functional HVS concepts to rock your implementations.

About the author

Alba Rivas works as a Principal Developer Advocate at Salesforce. She focuses on Lightning Web Components and Lightning adoption strategy. You can follow her on Twitter @AlbaSFDC.

Stay up to date with the latest news from the Salesforce Developers Blog

Subscribe