I built an automated SMS service in Salesforce using batch/scheduled jobs with the Twilio Rest API for my wedding to notify guests of wedding information and adhere to contactless communication during COVID

Posted by

Hello readers! So, my fiancé and I are getting married on September 5th, 2020. We were originally supposed to get married on June 20th, however like many, due to COVID, we were forced to delay the big day. If you have helped plan a wedding, then you know that the big things you nail down in the beginning like a “day of” coordinator, flowers, and venue are just scratching the surface of the many other items that will need to be planned later down the road. The devil is very much in the details and it’s not easy, especially during COVID.

One evening, we were discussing the reception and how we would go about communicating certain information to guests such as table assignments. Our main goal is to prevent guests from grouping together as much as possible. We knew that setting out physical table assignments was out of the question because it would require guests to congregate and look for their number together. Additionally, organizing something where people wait in line might be a hassle logistically and waste a lot of previous celebratory time. Immediately, we started brainstorming on how we could solve this and in general, make the wedding a safer and more memorable experience for everyone.

Initially, we tried looking for digital services that might be able to accommodate our need, however the more we looked, the more we found that these services were incredibly expensive or did not offer the customization needed to get the job done.

As you know, there is always that “aha!” moment in engineering and architecture that we are constantly chasing. I started to wonder: “what if we could prep a csv with our guests and the necessary data, create contacts under an account and build a relatively configurable integration with an SMS service that will solve our communication needs?”. Let’s start with the data load.

I asked my fiancé to provide a list of our guests, their phone numbers, and any secondary (plus one) guest that they would be bringing. Before loading the data into my personal Salesforce sandbox, I had to prep the Contact object with the necessary fields, layouts and record types to accurately depict a guest count that is viewable in Salesforce.

First, I created an Account record for the Ortiz Wedding and a few new fields on the Contact object to support the data load of our guests. After some light modifications to the excel spreadsheet to include the correct Account and record type association, we were live with our wedding guests in Salesforce!

Now comes the fun part. Twilo has an exceptional Rest API and their documentation is great, especially with Salesforce. First, I created a new custom metadata object to house all of the parent integration info called Wedding SMS Service.

Next, I needed messages. My beautiful fiancé was kind enough to write the content for me so I created another metadata object called Wedding SMS Message and linked it to the Service object. To give you an idea of how we were storing this content, here is an example:

With this structure, as new wedding requirements come up, we can easily create new message mappings with little coding effort to qualify the condition. All that is left is to start building out the batch and scheduled jobs to call the integrations

I created a simple batch class called TwilioSMSBatch that you can see below:

global class TwilioSMSBatch implements Database.Batchable<sObject>, Database.AllowsCallouts {

    String weddingGuestRecordId = Schema.SObjectType.Contact.getRecordTypeInfosByName().get('Wedding Guest').getRecordTypeId();
    public String msgType;

    global TwilioSMSBatch(String msgType) {
        this.msgType = msgType;
    }

    global Database.QueryLocator start(Database.BatchableContext BC) {
        // collect the batches of records or objects to be passed to execute
        String query = 'SELECT ID, FirstName, LastName, Secondary_Guest_First_Name__c, Secondary_Guest_Last_Name__c, Table_Assignment__c, Phone FROM Contact WHERE RecordTypeId = :weddingGuestRecordId AND Testing_Guest__c = TRUE';
        return Database.getQueryLocator(query);
    }
    global void execute(Database.BatchableContext bc, List<Contact> guests){
        // process each batch of records
        System.debug('what is the value of contacts? ' + guests);
        
        Wedding_SMS_Message__mdt smsMessage = [SELECT SMS_Message__c, Emojis__c FROM Wedding_SMS_Message__mdt WHERE Message_Type__c = :msgType];
        System.debug('what is the value of sms message? ' + smsMessage);
        for (Contact guest : guests) {
            String guest_msg;
            if (msgType == 'Notification of SMS Intent') {
                guest_msg = guest.FirstName + ', ' + smsMessage.SMS_Message__c + ' ' + EncodingUtil.URLENCODE(smsMessage.Emojis__c, 'UTF-8');
            } else if (msgType == 'Table Assignment') {
                guest_msg = guest.FirstName + ', ' + smsMessage.SMS_Message__c + ' ' + (Integer.valueOf(guest.Table_Assignment__c) == 0 ? 'the head table' : 'table ' + Integer.valueOf(guest.Table_Assignment__c));
            }
            TwilioSMSService.sendGuestSMS(guest.Phone, guest_msg);
        }
    }    
    global void finish(Database.BatchableContext bc){
        // execute any post-processing operations
    }    
}

The code above is a fairly standard implementation of querying for the contacts that meet a certain condition and passing them into the getQueryLocator(). When the execute method is invoked, we query for all of our custom metadata SMS Message records and see which message type given from the constructor matches the metadata record.

After, we can loop over all of our guests and based on the message type, construct a message thats appropriate for that given scenario. Once the message is constructed, we pass in the Phone number and the guest message to our TwilioSMSService integration to broadcast the message out.

The final step was to create a scheduled job class with a custom CRON expression so that I can run the job at the exact time I want. The CRON in the code example is just a test one that runs hourly:

global class GuestSMSIntentScheduler implements Schedulable {

    public static String Test_CRON_EXP = '0 0 0/1 1/1 * ? *';
    //public static String CRON_EXP = '0 0 15 5 1/1 ? *';

    global static String scheduleMe() {
        GuestSMSIntentScheduler guestSMSIntent = new GuestSMSIntentScheduler();
        return System.schedule('GuestSMSIntent', Test_CRON_EXP, guestSMSIntent);
    }
    
    global void execute(SchedulableContext ctx) {
        TwilioSMSBatch batch = new TwilioSMSBatch('Notification of SMS Intent');
        Database.executeBatch(batch);
    }
}

Of course, the most important part is the actual service that is being called to make all of this possible. See below for the TwilioSMSService

public class TwilioSMSService {
    

    public static void sendGuestSMS(String recepientPhoneNumb, String smsMessage) {

        Wedding_SMS_Service__mdt wedSMSServ = [SELECT Account_SID__c, Token__c, Twilio_Phone_Number__c, Twilio_Endpoint_URL__c, 
                                                   Twilio_Salesforce_Version__c, Twilio_URL_Parameters__c, Http_Method__c
                                                   FROM Wedding_SMS_Service__mdt 
                                                   WHERE DeveloperName = 'SMS_Wedding_Service'];

        HttpRequest req = new HttpRequest();
        req.setEndpoint(wedSMSServ.Twilio_Endpoint_URL__c + wedSMSServ.Account_SID__c + wedSMSServ.Twilio_URL_Parameters__c);
        req.setMethod(wedSMSServ.Http_Method__c);
        req.setHeader('X-Twilio-Client', 'salesforce-' + wedSMSServ.Twilio_Salesforce_Version__c);
        req.setHeader('User-Agent', 'twilio-salesforce/' + wedSMSServ.Twilio_Salesforce_Version__c);
        req.setHeader('Accept', 'application/json');
        req.setHeader('Accept-Charset', 'utf-8');
        req.setHeader('Authorization','Basic '+EncodingUtil.base64Encode(Blob.valueOf(wedSMSServ.Account_SID__c + ':' + wedSMSServ.Token__c)));
        req.setBody('To='+EncodingUtil.urlEncode(recepientPhoneNumb,'UTF-8')+'&From='+EncodingUtil.urlEncode(wedSMSServ.Twilio_Phone_Number__c,'UTF-8')+'&Body=' + smsMessage);

        Http http = new Http();
        HTTPResponse res = http.send(req);

        System.debug(res.getBody());
    }
}

As you can see, the first function, scheduleMe(), is meant so that I can easily execute it from Execute Anonymous and schedule the job to run. The second execute function is actually calling the batch with its associated message type. Each individual message will have its own scheduler class to pass in the appropriate message to the batch so the correct SMS gets broadcasted to our guests.

Below is an example:

Another cool part was that Saira thought it would be cute to include emojis in our messages. It was fun to figure out! You can see on line 24 EncodingUtil.URLENCODE(smsMessage.Emojis__c, ‘UTF-8’); I was able to take the emojis placed within the text field on the custom metadata record, URLENCODE them and reveal them inside of our text messages to our guests.

Here is another example of our table assignment text that will go out to our guests:

One really impressive thing about Twilio is their out of the box capabilities such as SMS Auto Response. I was able to create a custom flow through their studio and easily send out a custom auto response to guests who happen to text back to my Twilio number over the weekend.

I was even able to add a funny little GIF to the message. Here is a preview below:

The best part about all of this is that each text that I send costs 1 cent, which will allow us to have these features for our wedding for under $5.

Please let me know if you have any questions and happy coding!

4 comments

  1. Hey Taylor, it’s Lauren (from Apto). Have I left you a comment yet?? I can’t remember, but I have to leave one now because I am loving your blog so much. This wedding planner help using SFDC is so legit!!

    Liked by 1 person

    1. great call out. Im going to add it to the post now. Thanks so much for taking the time to read! and thank you! were excited for the big day

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s