How I Built This: Drive Your Survey Automations with Flow – Part 2

The Use Case

The customer wanted to update several fields on the related Case that tracked whether the person responded to the latest survey, and then update the Case status appropriately based on a specific question’s response. 

They also wanted to send an in-app alert to a Queue if the person responded extremely negatively to any of the numerical questions being asked in the Survey. So if they responded with a 0, 1, or 2 to the CSAT question, they wanted to know. 

Using Custom Metadata / Custom Settings to Automate Specific Questions.

In the previous article we talked about the Survey data model and a sample scenario with one question and 3 choices. We’ll use that in this new flow. Remember how I said it’s important to understand the Survey data model? That was no joke – you will likely be lost if you don’t take time to understand it. 

Just a caveat – this solution assumes you do not have Feedback Management, which has the super helpful Data Mapper solution which lets you do some of what Flow does, but without the Custom Metadata complexities introduced here. Feedback Management and Flow can live in tandem, however. If you need basic automations done in your system, Feedback Management is a great way to do that… However, if you need to do things like sending an in-app alert to a queue, or re-use automations across Surveys you’ll need some help from Flow.

I will say generically that if you have the same automation that needs to run for every survey in your system or is shared across many surveys, I recommend a Flow-first approach. This allows you to make changes to your automation in one place vs. doing it on a per-survey basis. Again, though it may be worth sacrificing 

Flow Entry / Early Conditions

I won’t go element by element unless demand arises, but I do want to call out a few checks you can make.

In the rare scenario where a Survey isn’t complete, we likely want to check that the survey is actually completed:

Next up we check that the Question Responses’s survey name matches what we’re expecting. That can be done by traversing upward: $Record.SurveyVersion.Name (or $Record.SurveyVersion.Survey.Name). We can’t do that in our entry criteria (yet), so we have to do it here in a decision node.

In my example below I actually have a custom lookup on Survey Invitation called ‘Related Case’, and I plan to update some fields on the Case, so I ensure there is actually a value there. That can be done by traversing upward as well: $Record.SurveyInvitation.Related_Case__c

Set Case Fields Based on the Survey Response

Next up is where I actually set some values on the related case to track the survey completion at the Case leve (not the Invitation/Response level). These fields on Case track that an evaluation was completed and when it was completed.

Notice I don’t do an Update here – I use an assignment to save on DML as I will likely be making more changes to the case downstream in the Flow. Efficiency!

{!recordCaseToUpdate.Id} Equals {!$Record.Invitation.Related_Case__c}

{!recordCaseToUpdate.Date_Evaluation_Complete__c}  Equals {!$Flow.CurrentDate}

{!recordCaseToUpdate.Evaluation_Status__c} Equals Complete

Set Case Fields Based on a Specific Response

As Anakin Skywalker once said, “This is where the fun begins”… and hopefully not “I’ve got a bad feeling about this”. 

The first thing we want to do is grab all of the Custom Metadata Mappings related to this survey.

Step 1

Do a null check on the results just to make sure you’ve got some records, then things get a bit more interesting and purely based on your use case and what you’re trying to do. 

In my Flow I looked for any questions that were specifically Choice questions (so no CSAT/numerical questions). This was done by checking that the QuestionChoiceId field (the lookup to the actual Choice in the question) is not null:

Step 2

My survey only had 1 possible question with a choice, so you may want to consider adding more criteria to future proof things.

Step 3 & 4

Next up is where I wish we had more collection processing power without assistance of invocables, but alas, this was my solution. I looped over the CMT mapping records I grabbed in Step 1, then I did another loop (GASP!) on the collection of responses above. I felt comfortable doing this because I was only ever going to have 1 response record x 3 mapping records, so there was no risk of hitting the dreaded 2,000 iteration limit. 

Note: This is needed because we can’t reference the DeveloperName of the responses’s related question record on the left side of a Get records element yet. Another added complexity is that we cannot directly reference CMT records like you can in Process Builder, so we have to retrieve them with Get elements.

Step 5

In the first decision we match up a Custom Metadata record that stores the Question Choice DeveloperName with the response’s Question Choice DeveloperName. Once we get that match, we move forward.

{!Loop_Over_CMT_Records.Question_Choice_DeveloperName__c} Equals {!Loop_Over_Found_Choice_Responses.QuestionChoice.DeveloperName}

Step 6

In the loop I check to see that the Response record’s related Choice Matches a particular outcome stored in the Custom Metadata field Question Choice Text. This is where we route the flow based on the automation we want.

{!Loop_Over_CMT_Records.Question_Choice_Text__c} Equals Request_Met

Step 7

From there, the world’s your oyster.  In my flow, I had 3 outcomes that did different things to the case. Remember how we used an assignment element for the Case earlier instead of an Update? That’s because we’re doing another update to it here:

Summary (In Words)

  1. Check that the Response’s Survey ($Record.SurveyVersion.DeveloperName) is the one you want to automate.
  2. Get all of the Survey Automation Mapping custom metadata records for the particular survey.
  3. Get all of the SurveyQuestionResponse records that have a related Choice record.
  4. Loop over the CMT Mapping records
  5. Loop over the found SurveyQuestionResponse that are choices (loop within a loop only if the expected responses with choices is tiny)
  6. In the loop, do a decision to match up the looped Custom Metadata record that stores the Question Choice DeveloperName with the 2nd loop’s Question Choice DeveloperName
  7. In the next decision, create your decision paths for each of the results you want to handle using the Question_Choice_Text__c field on the custom metadata record
  8. Do your thang!

Send an Alert to a Queue on a Poor Responses


Okay this one’s a bit easier. A lot easier, actually.

One nifty thing I’ve done is setting up an in-app alert to go to a specific queue when somebody responds poorly (1,2, or 3 out of 10) to a CSAT question. 

Step 1 

Grab any question responses that have a specific numerical value tied to it. Note that I use the Id of the record that triggered the Flow (the Survey Response’s record id)

Step 2

Do your null/empty check on this step ^

Step 3  (Optional)

Here we do some stuff to the case if needed.

I actually set the case status to ‘Problem’ if we found any matching results.

Step 4 (Optional)

Loop over the questions to store the question text

We Loop over the question responses to create a text variable of all of the question names for the alert we’ll make. This will go into the alert body.

The left side is a Text Collection variable of your choosing, the right side is: {!Loop_Over_Question_Names.Question.Name}

If you’re a big USF fan like me, you’ll have the handy ExtractStringsFromCollection action to do this for you, but if you don’t want to use that, you can go this route. 

Step 5 – Send the Alert

Next up is standard fare for using the Alert standard action in Flow. I made a post about it a year and a half ago on LinkedIn before I joined the UnofficialSF world: 

Your Notification Body can be a text template that references your text collection variable of the questions that were responded to poorly.

Step 6 – Do your Last Updates

We finally update the Case with all the various updates made in various assignment elements throughout the Flow:

And you’re done! I’d love to hear everyone’s thoughts on this – I know it’s a bit complex but once you get it going it can really scale quite well and you won’t need to make edits to your Flows if you make text changes to your Surveys.

How I Built This: Drive Your Survey Automations with Flow – Part 1

I’ve seen quite a number of people asking how to drive automations using Salesforce Surveys and Flow and thought I would share a solution I’ve used in a recent project that utilizes Custom Metadata records to intelligently drive automation based on specific responses.

First, I will say that for basic automations the Surveys add-on called Feedback Management will handle most scenarios for you – simply create a data map for each survey and perform a series of automations in your org. For more advanced automation, or if you can’t purchase Feedback Management, you may want more control and flexibility with the help of Flow.

Secondly, I will also say this is not the most basic solution. This takes some time to digest and if you want to sacrifice scalability for speed, you don’t need Custom Metadata records… however you will need to be careful of any changes made to the survey and the logic used in your flow that may look at the question text. Understanding this model will serve you well in the long run – both in knowing how to use Custom Metadata and in understanding the Survey data model.

The Survey Data Model

It’s critical to understand the Survey Data model in order to understand the Flow automation I built to handle survey responses. When a survey taker responds, a Survey Response record is made, with a series of Survey Question Responses for each responded question.

Please excuse this extremely un-architechty diagram.

Survey: This is the actual survey you create and has multiple versions. Everything eventually rolls up to this.

Survey Version: This is where we track the various versions of the related survey. Think of this as a similar version of ContentDocument / ContentVersion.

Survey Question: The questions that you create on your survey each have their own Question record that all relate to the Survey and Survey Version.

Survey Question Choice: If your question has multiple options (picklist, for example), you will have multiple Survey Question Choice records that all relate to a single Survey Question.

Invitations & Responses

Now we get into when a survey taker responds to the survey.

Survey Invitation: This is where the unique URL is made for the survey. This is also where you specify other details like if the user needs to be logged in to respond, and whether  You can add custom fields to this.

Survey Subject: A junction object that connects the survey response to another object. You can add custom fields here. This typically grants visibility to the survey.

Survey Response: Represents information about a participant’s response to a survey, such as the status of the response, the participant’s location, and when the survey was completed.

Survey Question Response: This is the actual response to a question. You can choose to drive your automations on this object, or Survey Response.

Take this SOQL query for example that looks at the out of the box Customer Satisfaction Survey choices. SurveyQuestionChoice looks up to SurveyVersion and SurveyQuestion. We use the relationship fields just to demonstrate how they all relate.

SELECT Id,Name,SurveyVersion.Name,Question.Name 

FROM SurveyQuestionChoice 

Where SurveyVersion.Name = ‘Customer Satisfaction’

Which object do you select as your starting point?

The first thing you need to consider is whether you want your automation to run when a Survey Response is created vs. when a Question Response (a response to an individual question) is created. Both have valid use cases.

Trigger on Survey Response

Trigger your flow here when you only want your Flow to fire once. 

  1. If you have a ton of questions with potentially complex logic and lots of looping, I would suggest starting here. 
  2. If you really only care that somebody has submitted a survey and you want to track its status elsewhere (like on a Case), go this route. Otherwise, you will needlessly fire off a Flow for every single question.

Trigger on Survey Question Response

Trigger your flow when you want your Flow to fire for every question. 

This works well when you don’t have a ton of questions and you don’t necessarily care about doing something with the status of the overall survey.

If you don’t necessarily care what happens on submission and you’re okay with multiple instances of your automation firing, choose this object as your starting point. 

Be wary of going this route if you have something like 50+ questions though, you could run into limit issues if your flow is complex and has lots of loops / processing. 

Custom Metadata: Survey Automation Mappings

The main thing that drives this Flow is the Survey Automation Mapping custom metadata object. In it, we store the developernames of specific Choice and Survey records. This allows us to avoid hard-coding responses in decision nodes in our Flow to drive automation on specific responses to questions. This means that we can change the answers to questions in surveys without worrying about also updating the Flow. 

Here are the fields:

Active: Keeps the mapping activate. Generally always a good idea to have an Active flag in any CMT record.

Department: Not needed for generic use, but could provide for department-specific automations for the same survey responses.

Mapping Help Text: Provides the admin the ability to provide context / help for the mapping record like where and why it’s used

Question Choice DeveloperName: Stores the developer name of the Question Choice – we do this so we dont have to reference static text in the automation.

Question Choice Text: Stores the answer response text – used in the decision node in the Survey Response Flow

Question Label: Optional. Used more as a handy reference when viewing mappings to know which question is being referenced in this CMT record.

Question Query Text: Used by Flow to retrieve the right CMT record and get the developer name of the question being queried. For example ‘Was_Request_Met’ AND Survey DeveloperName = ‘Surveyname’

Survey Developer Name: Stores the developer name of the survey containing the mapping

Each response to a ‘Choice’ type question has a related Choice record for each option for that question that has a flexible label, what the customer sees, but a static DeveloperName. We use that DeveloperName in the custom metadata record. See below for an example of how the data model looks with Questions, Choices, and Responses. 

Let’s take a scenario where there is a question on a survey that asks ‘Was your request met with us’ with 3 possible choices (see below). 

Getting the DeveloperName of Questions & Surveys

You will need to use SOQL (Dev Console, Workbench) or Salesforce Inspector’s (a Chrome Extension) ‘View Data’ feature to see a survey/question/choice record’s DeveloperName. VS Code also has some SOQL functionality.

Here is how you’d query for everything you need to create your Metadata Mappings:

SELECT id,Name, DeveloperName,Question.Name, Question.DeveloperName, SurveyVersion.Survey.DeveloperName

FROM SurveyQuestionChoice 

WHERE SurveyVersion.VersionNumber = (use the version # you want here)
AND SurveyVersion.Name = ‘Survey Name Here’


Using this, we can create some mapping records:

ActiveLabelSurvey Automation Mapping NameMapping Help TextQuestion DeveloperNameQuestion LabelQuestion Query TextQuestion Choice TextSurvey Developer NameQuestion Choice DeveloperName
TRUEMember Feedback – Request Not MetMember_Feedback_Request_Not_MetUsed to automate Case field ‘Request Status’ to drive if the case has to be re-opened or can be closedq_963113e4_d736_4a0d_b992_7f42bdb540e1Was your request met with us?Was_Request_MetNot_Met_Close_Casecustomer_satisfaction_surveyc_315e2827_6ffc_49d4_93b9_54b55cd356
TRUEMember Feedback – Request Not MetMember_Feedback_Request_MetUsed to automate Case field ‘Request Status’ to drive if the case has to be re-opened or can be closedq_963113e4_d736_4a0d_b992_7f42bdb540e1Was your request met with us?Was_Request_MetRequest_Metcustomer_satisfaction_surveyc_69a6ba6f_0aec_4f1d_8283_feff74543339

Next up – we’ll look at using these custom metadata records to drive specific responses in our Flow

Survey Invitations Demystified: Part 1 Builder

You can generate several types of invitations for your survey depending on the business use-cases of your organization. Some of the use cases would require the survey api’s while for certain others you can use the default invitations provided in the builder. In a series of posts, we are going to be discussing some of the options to generate survey invitations. This post talks about survey invitations you can generate from the survey builder. Read on to find out more

A few housekeeping stuff before we get into the nitty-gritty of invitations

Communities: For all surveys that involve external participants such as a contact or a lead there needs to be an active community setup. Communities come free with most editions of Salesforce and it is fairly easy to set one up.

Authenticated Surveys: Surveys rely on salesforce SSO for authenticating users and employees. For authenticating contacts and leads, you will need to rely on the authentication capabilities given by communities and this may involve the purchase of additional licenses. Authenticated survey responses are tied back to the participant. All authenticated survey invitations expire after a single take!

AnonymousSurveys: Survey invitations can be anonymous, which means that no information about the respondent is captured, the responses hence cannot be tied back to a participant and a participant can take this survey as many times without the link expiring.

Identified Surveys (aka Identified links): There are tons of use-cases where a survey would need to be tied back to the respondent but at the same time you don’t want to use authentication (SSO or want the customer to log in with a user name and password) before submitting the survey. This is where identified surveys come in and the idea here is that the survey invitation is generated with the information about the participant and these will expire after a single take.

Invitations generated by the Survey Builder: Get Survey Link

Types of invitations supported: Authenticated Surveys, Anonymous Surveys

As soon as you activate a survey, the survey builder generates 2 sets of invitations

  1. A link for particpants in your company like users and employees
  2. A link for particpants outside your company like contacts and leads

                                    Survey Builder -> Get Invitation

You will notice that there is a check box to anonymize the invitations in this modal. These links can be sent out via various email clients or even via the marketing cloud. There is an option to download the QR code equivalent of these links as well

Invitations generated by the Survey Builder: Send Email

Types of invitations supported: Authenticated Surveys, Anonymous Surveys, Identified Surveys

You have the option to send a survey via email directly to a single recipient or to a list in salesforce from the survey builder. The invitation link settings control the type of invitation

Here is what each of the options mean:

a) Unique link is the same as an identified survey.

b) Anonymize responses will obfuscate the survey responses

c) Don’t require authentication ensures that users are not asked to log in before taking the survey

d) Autoexpires gives you the flexibility of expiring the survey in a future date

In the next post we will take a look at some of the other ways to distribute surveys via process builder and flows