SMS Lead Generation with the Twilio Library for Salesforce | Salesforce Developers Blog

I spend a lot of time on identity and integration, but I recently got back to’s roots in sales force automation and customer relationship management with some work on SMS-to-Lead.

“SMS-to-Lead?”, you ask? Well, as you probably know, Web-to-Lead is a core feature of the platform, allowing you to create Lead records directly from a form on your website, a cornerstone of CRM functionality. In a recent blog post, Sandeep combined Facebook, Heroku and to create Social Web-to-Lead. That, together with Twilio’s release of their library for Salesforce, got me thinking about SMS-to-Lead.

The base concept is very simple – run a campaign inviting prospective customers to text their email address to find out more about your product, receive some freebie, whatever. It turns out that the execution is almost as simple as the concept. Today I’ll walk you an Apex class that does exactly that, in less than 100 lines. (Here’s the code in its entirety).

First, I define the class, with a @RestResource annotation. This is a RESTful web service that will be deployed at the relative URL /smstolead

global class SmsToLead {

I’ve already installed the Twilio library and configured a custom setting with my Twilio account credentials, so I can get a reference to my Twilio account by just calling TwilioAPI.getDefaultAccount():

static TwilioAccount account = TwilioAPI.getDefaultAccount();

Now I define a method that will accept an HTTP POST from Twilio:

global static void incomingSMS() {

I’ll be sending an email, so I reserve capacity up front to do so. This ensures that, if I’ve already reached my daily email limit, an error will be thrown before any more processing is done.

// This will error out with System.LimitException if we would exceed 
// our daily email limit

Having done that, since this is a web service deployed at a public endpoint, it’s important to verify that this POST really did come from Twilio. I do that by gathering the signature passed in via the X-Twilio-Signature header, the requested URL, and request parameters, and passing them to the validateRequest() method in the TwilioRestClient class. validateRequest() calculates an HMAC of the request data, using my Twilio auth token as the key (the auth token is a secret that I share with Twilio), and comparing it to the signature sent in the request. If the calculated HMAC matches the signature from the header, I can be confident that this request came from Twilio and it was not modified in transit. I’m setting the response body in the sample to aid debugging, but, in production, you would return the 403 status with no body.

String expectedSignature = 
String url = 'https://' + RestContext.request.headers.get('Host') + 
    '/services/apexrest' + RestContext.request.requestURI;
Map <String, String> params = RestContext.request.params;

// Validate signature
if (!TwilioAPI.getDefaultClient().validateRequest(expectedSignature, url, params)) {
    RestContext.response.statusCode = 403;
    RestContext.response.responseBody = 
        Blob.valueOf('Failure! Rcvd '+expectedSignature+'nURL '+url/*+

Now I’ve validated the signature, I set up a response, since Twilio will log a 502 error if I respond with HTTP status 200, but no body.

// Twilio likes to see something in the response body, otherwise it reports
// a 502 error in
RestContext.response.responseBody = Blob.valueOf('ok');

That out of the way, I can pull data out of the request parameters:

// Extract useful fields from the incoming SMS
String leadNumber = params.get('From');
String campaignNumber = params.get('To');
String leadEmail = params.get('Body');

I’ve added a custom field to the Campaign object so I can automatically associate the incoming SMS with a marketing campaign I’m running. Here I use the phone number from the SMS to locate the Campaign record. If there is no matching campaign, I send an SMS reply with an error (see below for the reply method). You would only expect to see this error in early testing, if a Campaign record had not been created, or was configured with the wrong number.

// Try to find a matching Campaign
Campaign campaign = null;
try {
    campaign = [SELECT Id, Name, NumberSent 
        FROM Campaign 
        WHERE Phone__c = :campaignNumber 
        LIMIT 1];
} catch (QueryException qe) {
    reply(campaignNumber, leadNumber, 'No Campaign configured. Sorry.');

Now I have the Campaign, I insert a Lead. Note that Leads are not directly associated with a Campaign – I’ll need to create a CampaignMember record later. I set the Lead’s LastName and Company to ‘From SMS’, since these are required fields but I don’t have real data to put there. In a real marketing campaign, these would likely be updated later on as the sales team engages with the lead.

I don’t bother doing any validation on the email address from the SMS, since the platform will do that for me when I insert it, throwing a DmlException if the value in the Email field does not appear to be a valid email address. Note that well-formatted but non-existent email addresses will be accepted, so you might want to use a service such as StrikeIron’s Email Verification in a real production setting.

// Create and insert a new Lead
Lead lead = new Lead(LastName = 'From SMS',
    Company = 'From SMS',
    Email = leadEmail, 
    Phone = leadNumber);

try {
    insert lead;
} catch (DmlException dmle) {
    String message = (dmle.getDmlType(0) == StatusCode.INVALID_EMAIL_ADDRESS)
        ? leadEmail+' doesn't look like an email address. Please try again.'
        : 'An error occurred. Sorry.';
    reply(campaignNumber, leadNumber, message);

Now I can link the Lead to the Campaign.

insert new CampaignMember(CampaignId = campaign.Id, LeadId = lead.Id);

I send an SMS reply to thank the texter:

reply(campaignNumber, leadNumber, 'Thanks for registering. We'll send you a confirmation email!');

Let’s take a look at the reply() method that I define further down in the SmsToLead class. I need that @future annotation since sending an SMS invokes a callout to Twilio, and I can’t execute a synchronous callout, since I’ve made changes to the database that are not yet committed. @future solves the problem by making calls to reply() asynchronous – they go on a queue and are executed shortly afterward.

public static void reply(String fromNumber, String toNumber, String message) {
    Map<String, String> params = new Map<String, String>{
        'From' => fromNumber, 
        'To' => toNumber, 
        'Body' => message

    TwilioSms sms = account.getSmsMessages().create(params);
    System.debug('Sent SMS SID: '+sms.getSid());

With all that done, all that remains is to send the texter an email. In a real campaign, I’d likely include a link, allowing the user to opt in to the campaign and provide more of their details. Here I send a simple acknowledgment. Using setTargetObjectId(), rather than setToAddresses(), allows the platform to record this email as an activity against the Lead. This is the default behavior when you specify a Lead, Contact or User Id this way, but I call setSaveAsActivity() to make it explicit in the code.

// Send an email, recording it as an activity
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
mail.setPlainTextBody('Thanks for registering!');
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });

Download the full source code here.

With the Twilio library installed and configured, and the SmsToLead class in place, you’ll need to configure a site and follow the instructions here to set up a publicly accessible endpoint of the form Create a phone number at Twilio, and set your new endpoint as its SMS Request URL:

You should be able to text your email address to the Twilio number, and see a new Lead in your org, complete with an email address, phone number, an email in its activity history and a campaign listed:

So, there you have it – SMS-to-Lead. How are you acquiring leads in your org? Share your experience in the comments…

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