How I Built This: Use Generate Collection Report, Extract Strings from Collection and Send Better Email to Keep Users/Clients Up to Date

At my current organization, we built and maintain an Experience Cloud Site where Employers engage with Candidates about open opportunities and internships along with a large networking event held in the fall. Employers will post their job/internship opportunities on the site and candidates can one-click apply to these specific roles. As candidates build their profile and update their profile they have an option to receive a notification either daily, weekly, or monthly about recently posted opportunities matching the timeframe selected. Sending these notifications used to be sent a long time ago by someone literally copying and pasting a list into an email and sending them out. Enter Generate Collection Report, Extract String from Collection, Send Better Email, and Scheduled flows. Now this is an automated process.

Step 1 in the flow was setting the criteria for the schedule in this case I am not doing this for a specific record but using 2 collections so I just set the frequency and start time and not object in my start element.

Step 2 get all of the Job & Internship Postings that have been created within the last 24 hours and are published. I have a formula for today that is {!$Flow.CurrentDate}-1

Step 3 decide if there were any postings to send all we are doing here is looking to see if our Get Job & Internship Postings returned any records.

Step 5 create the Collection Report using Generate Collection Report. This apex action allows you to input a record collection and return specified fields into an html formatted report/table. For full details on this component check out this post.

Step 6 get Experience Site Users to send the notification to. (note for new flow users see how I am using a label to match the ProfileId try and not to hard code ids in flow)

Step 7 update the Lightning Email Template that is used in the email send with the updated Job & Internship Postings. The only thing I am updating is the outputvalue from the Generate Collection Report action.

Step 8 get userIds from our user collection using Extract Strings from Collection Action which can be found as part of the Collection Processors package

Step 9 use Send Better Email to send a mass email with a template to the candidates

Finally, Sit back relax put on your superhero cape and sip on your drink of choice.

From Aidan Harding & Nebula Consulting: Flow for Developers

This blog post covers a range of topics interesting to Apex developers evaluating Flow’s strengths and weaknesses, including test automation and map support:

Check it out!

Convert To Flow Now Generates Before-Save Triggers and Subflows

Support for Process Builder Processes that Launch Flows

The latest update of Convert To Flow adds support for Process Builder processes that launch flows, converting them to Triggered Flows that use a Subflow.

NOTE: this requires a Winter ’22 Org.

The Newly Generated Flow Will Use Before-Save Triggers If Possible

If the source process or rule does a straightforward field update, a high-performance Before-Save trigger will be used. If the source process does something that’s not allowed Before the Save, an After-Save trigger will be used.

Bug Fixes

Several bugs have been fixed that were preventing successful conversion.

Check it out here.

From Josh Dayment Updates to Flow Barcode Scanner

This update provides more customization to the component and added functionality to include auto navigation on successful scans.

Property Name Data Type Description
Auto NavigateBooleanWhen marked as true the flow will automatically move to the next screen/element upon a successful scan.
Button IconStringThis is an icon that will be placed on the scanner launch button uses lightning design system i.e. utility:photo
Button LabelStringThe label to on the scanner launch button.
LabelStringLabel placed in front of scanned data.
Scanner InstructionsStringText that sits above scanner launch button to provide scanning instructions to the user.

View Full Details and Download

From Suraj Pilai and Hijlko: CSV File Import Gets Some Great Internationalization Improvements

The ConvertCSVToRecords action has been upgraded to add the following features:

Support for Non-US Separators

Contributor Hijlko added this useful improvement. With it, you can import CSV files containing values that use non-comma separators like semicolons. Standard US csv uses a comma as a field separator while most European csv’s use a semicolon as a field seperator.The code can now import muli region CSV files
Numbers/currency like 1,2345.56 (US) and 1.234,56 (EU) are converted to 1234.56

You can now set the following:

  • Field Separator
  • Thousand Separator
  • Decimal Separator
  • Currency Symbol

If these inputs are left empty, the existing default values will be used.

Support for Non-US Dates

Date values in the formats YYYY-MM-DD and DD-MM-YYYY (or with non-hyphen separators like YYYY/MM/DD and DD/MM/YYYY) are now automatically converted to YYYY-MM-DD, which is the Salesforce standard.

Additional Robustness in Handling Non-Alphabetical Characters

Contributor Suraj Pilai has improved this action so that CSV’s with the following characteristics will now be properly processed:

  • Rows/File beginning and ending with quotes
  • Newline characters within columns
  • Null columns at the end of a row
  • Supports the CSV spec definition of double quotes, which is represented by consecutive double quotes ("") within a column enclosed in double quotes:

Option to Trim Leading and Trailing Spaces

Set isTrim to true to have any leading or trailing spaces removed.

Install here.

A New Home Page for Info about Flow Templates

Check out this new Wiki page:

Everyone (you too!) is encouraged to propose edits to the wiki pages to grow the quality of the wiki as a knowledge base. Just hit edit, and start typing and pasting. Your content will get automatically reviewed so you don’t have to worry about mistakes.

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

July and August Community Flowcast

The Flowcast is an attempt to summarize the key posts and community developments around Flow in a ‘bite-size’ format each month. I just posted the newest Flowcast for July & August – I hope you enjoy it!

Check the video comments for hyperlinks to the various articles.

Available: sendBetterEmail – Scheduled Release 2.2

Now Available sendBetterEmail scheduled release 2.2 Unlocked (Ver 2.1.7) . Enhancements and improvements; #855 allow installation when enhanced email not enabled and #862 add to Category “Email” in new action insert.