Send Mass Template Messages


This article describes how to send template messages from a Salesforce object to multiple recipients at once. For instance to send WeChat Template messages to CampaignMembers linked to a certain Campaign. For this guide, it is assumed the template messages and the Social25 environments are already configured and set-up. The trigger in this example will be a button press event on a Lightning Component, but this might also be a trigger, a process builder or a flow. 


Note: Consent management

Consent is managed through the platform through which the messages are sent. In the case of WeChat, subscribing to your account. In the case of WhatsApp, consent is not technical but a legal requirement and pre-approved templates have to be used.

Technical Overview

Social25 has functionality to send pre-defined template message to a single or a collection of users. Marketing users may configure the template and the related merge fields, while an admin or a developer takes care of collecting the necessary info and providing this to the Social25 Post_Template_Message interface.  Social25 then takes care of merging the fields and sending the template to the provided contacts (using the Heroku Conversation Id). This can be done using Process Builder and Flow (through the Invocable Method) or through Apex. 

As mentioned Social25 does the heavy lifting of preparing the template and sending the messages. Salesforce still needs to tell Social25 what template to send, with what information, to whom, and when. The documentation below explains these steps.

This guide contains code snippets and examples that may be used. There are other ways to implement this, the best way is left to the digression of the reader. Code snippets are provided as-is, without any warranty or guarantee.  

Method Signature

To send template messages, Social25 has a built in call message:

Send Template Message method signature
global static List<Post_Message.PostMessageResponse> Social25.Post_Template_Message.call(List<Social25.Post_Template_Message.PostTemplateMessageRequest>)

This method accepts a list of PostTemplateMessageRequest objects, a Social25 custom class. The method returns a list of PostMessageResponses containing the individual request status. These are void when called from an asynchronous context. 

Request Class Description

The class TemplateMessageRequest contains the following accessible properties:

NameDataTypeDescription
asynchronousBooleanSets whether the messages should be sent asynchronously or not. 
conversation_idStringThe Social25 Heroku Conversation Id. Found on the Heroku Message object, and on the related Case, Opportunity or Lead. 
template_nameStringThe Name of the Template defined. Used to fetch the Template definition.
record_idIdThe record Id to use when merging. 

Set up the template message

The packaged Template functionality in Social25 consists of two parts; the Template object and the Template Field object, following the principle of the Integration Definition and the Integration Field objects used in some implementations. The Template defines the overall structure of the template, the Template Fields define the merging of the fields.

Template context

The Template consists of two relevant fields:

  • the Name, used to identify the template,
  • the Structure, containing the (often JSON) content of the template expected by the platform. For WeChat (example below) the template_id, and the merge fields are defined. The actual structure is defined in the Business Manager or similar platform app,

Optional fields can be added to customize the template. For instance (in the below example) a Query Filter is added that is used to exclude certain records when sending messages (see Filter Criteria below). 

 

Merge Fields

The merge fields consist of four relevant fields:

  • Template Id; used to link the template fields to the relevant Template,
  • Name; the value used in the template definition,
  • Merge Destination; the field on the related record that contains the desired value,
  • Active; whether this Merge Field should be used or not (may be used to quickly switch between configurations).

The below example shows the Merge Field setup for the given example template. Fields are linked to the corresponding values in the template. 

Note that all field destination values are taken from the triggering object (in this example, Campaign). Simple cross object definitions up to one level deep can also be used (example: Owner.Name, or Location__r.Description__c, but not Primary_Contact__r.Account.Name). This limitation can be mitigated by using for instance cross-object formula fields. 

Filter Criteria

Filter criteria may be used to exclude certain messages when sending. For instance, internal or external users may be excluded, or consent may be kept on the related record level and filtered in the criteria. This filter is appended to the SOQL query that is executed on trigger. See code snippet for more information. 

Set up Salesforce and Custom Component

Sending the actual messages in this example will be done based on a button push on a custom Lightning Component. This component consists of the Component, JS Controller and Helper, and Apex controller. The component lists the available templates and sends all messages (except those excluded by the filter criteria) the defined template message by triggering a Batch. 


Apex controller boilerplate:


Example Lightning Controller
@AuraEnabled 
public static void sendWeChatTemplateMessages(Id templateId, Id campaignId) {
   //Query the selected template to retrieve the name
   Social25__Template__c selectedTemplate = [SELECT Name FROM Social25__Template__c WHERE Id = :templateId];
   //Start the job to send the template messages to the campaign members. CampaingId is sent to the batch constructor.
   Id jobId = Database.executeBatch(new G25_Batch_SendS25TemplateMessages(selectedTemplate.Name, campaignId), 99);
   //Do something with the jobId for logging purposes and such...
}

Apex Implementation example

This starts a batch, here we show the start() and execute() methods (some functionality such as logging omitted for brevity):

Batch Start
public Database.QueryLocator start(Database.BatchableContext bc) {
   Social25__Template__c template = [SELECT Query_Filter__c FROM Social25__Template__c WHERE Name = 'Meeting Event'];
   //this.campaignId is set by the batch constructor, as input from the Lightning controller.
   String queryString = 'SELECT Id, Conversation_ID__c FROM CampaignMember WHERE CampaignId = :this.campaignId AND Conversation_ID__c != NULL';

   //append a query filter to the query,
   if(String.isNotBlank(template.Query_Filter__c)) {
      queryString = String.join(new List<String>{queryString, template.Query_Filter__c}, ' AND ');
   }
   return Database.getQueryLocator(queryString);
}


Batch Execute
public void execute(Database.BatchableContext bc, List<CampaignMember> campaignMembers) {
   //Keep a mapping of the Conversation Id to a specific Campaign Member. The conversation Id is set on the
   //campaign member using logic out of scope for this guide. 
   Map<String, CampaignMember> weChatIdToCampaignMember = new Map<String, CampaignMember>();
   for (CampaignMember member : campaignMembers) {
      weChatIdToCampaignMember.put(member.Conversation_ID__c, member);
   }

   //Keep a list of the requests that needs to be send out
   List<Social25.Post_Template_Message.PostTemplateMessageRequest> postTemplateMessageRequests =
      new List<Social25.Post_Template_Message.PostTemplateMessageRequest>();

   //Loop over the campaign members of current batch
   for (CampaignMember relatedCampaignMember : campaignMembers) {
      //Create a request object and fill in the required attributes
      Social25.Post_Template_Message.PostTemplateMessageRequest templateMessageRequest =
         new Social25.Post_Template_Message.PostTemplateMessageRequest();

      templateMessageRequest.asynchronous = false;
      templateMessageRequest.conversation_id = relatedCampaignMember.Conversation_ID__c;
      templateMessageRequest.template_name = 'Meeting Event';
      templateMessageRequest.record_id = campaignId;

      postTemplateMessageRequests.add(templateMessageRequest);
   }

   List<Social25.Post_Message.PostMessageResponse> responses = new List<Social25.Post_Message.PostMessageResponse>();

   //do callouts. errors are handled by the package, so no try{} catch(){}.
   //results are stored in the request object (postTemplateMessageRequests).
   Social25.Post_Template_Message.call(postTemplateMessageRequests);
}

Error handling

When called from a non synchronous context, use the PostMessageResponse object to check and handle any errors. Social25 is set up to handle and report back most errors in this way. As handling is different in each Salesforce org, no further documentation is provided here.  

On this page