FrontlineSMS is an open source application used primarily in the developing world for communicating with large numbers of cell phone users. It’s a locally installed application that pushes SMS messages out through your tethered cell phone. It’s a really cool app with all sorts of great real-world usage. My friend Dale Zak just built an awesome reminders module for it, and that got me looking at it again.

The newest version of FrontlineSMS (1.6) has a very simple API for sending SMS messages. I have been successful in getting Salesforce.com to use this API to send SMS messages to single Contacts and to Leads and Contacts who are members of a Campaign. Here’s a short video of it in action:

Here’s what’s going on in the one-off send:

  1. I’m using Visualforce to create UI that is called by the Send SMS buttons
  2. The Visualforce page accepts the SMS text input
  3. Clicking send shows an iFrame that calls the FrontlineSMS API correctly: http://localhost:<port>/sms_number/sms_message
  4. A Task is related to the Contact with the data and content of the SMS message

Why use the iFrame? Since FrontlineSMS is locally installed, we have to communicate with it from the browser. A more robust option would be to create Javascript that makes the localhost calls and listens for the success or failure and proceeds accordingly.

To all the members of a Campaign, we do the following:

  1. The Visualforce page accepts the SMS text input
  2. Clicking Send exposes an Action Poller, which calls code every 5 seconds
  3. When the Action Poller calls code, 10 Campaign Members of the correct Status are queried
  4. 10 iFrames are populated with the FrontlineSMS API URLs for the messages
  5. The Campaign Member records have their Status changed so they won’t be queried again
  6. Tasks are related to the Campaign and the Contact or Lead

Here are the Salesforce code bits.

Visualforce page for one-off sends:

<apex:page standardController="Contact" extensions="extSendSMS" >
	<apex:form >
		<apex:sectionHeader title="FrontlineSMS" subtitle="Send SMS via FrontlineSMS">
			<description>
				Send an SMS using FrontlineSMS:
				<ul>
					<li>Install FrontlineSMS on your computer</li>
					<li>Make sure FrontlineSMS is running</li> 
					<li>Make sure your phone is connected to FrontlineSMS</li>
				</ul>
				
			</description>
			
		</apex:sectionHeader>
		<apex:pageMessages id="pageMessages"/>
		<apex:pageBlock id="smsblock" title="">
			<apex:pageBlockSection id="smssection">
				<apex:outputField value="{!contact.MobilePhone}"/>
				<apex:inputField value="{!task.Description}"/>
			</apex:pageBlockSection>
			<apex:pageBlockButtons id="smsbuttons">
				<apex:commandButton value="Send SMS" action="{!sendSMS}" rerender="smsiframe"/> 
				
				
			</apex:pageBlockButtons>
		</apex:pageBlock>
		<apex:pageBlock id="smsiframe" title="">
		<apex:iframe id="frontlinesmsIframe" src="{!endpoint}" scrolling="no"  rendered="{!showFrontlineSMSiframe}"/>  
		</apex:pageBlock>
	</apex:form>
</apex:page>

Apex controller for one-off sends:

public with sharing class extSendSMS {
	public Task task {get;set;}
	public String endpoint {get;set;}
	public Boolean showFrontlineSMSiframe {get;set;}
	private Contact contact;
	private Lead lead;
	public extSendSMS(ApexPages.StandardController stdController) {
		this.contact = (Contact)stdController.getRecord();
		this.contact = [select id, MobilePhone from Contact where Id=:contact.id];
		this.task = new Task();
		this.task.WhoId = contact.Id;
		this.task.type = 'SMS';
		this.task.ActivityDate = Date.Today();
		
		this.task.status = 'Completed';
		showFrontlineSMSiframe = false;
    }
    public PageReference sendSMS(){
    	this.task.subject = 'SMS: ' + this.task.Description; 
    	insert task;
    	String strippedMobilePhone = '';
    	strippedMobilePhone = this.contact.mobilePhone;
    	strippedMobilePhone = strippedMobilePhone.replaceAll('[^0-9]', '');
    	
    	endpoint = 'http://localhost:8565/'+ strippedMobilePhone +'/'+ this.task.Description;
    	showFrontlineSMSiframe = true;
    	return null;
    }
}

Visualforce page for Campaign sends:

<apex:page standardController="Campaign" extensions="extSendSMSCampaign" >
<apex:form >
		<apex:sectionHeader title="FrontlineSMS" subtitle="Send SMS via FrontlineSMS">
			<description>
				Send an SMS using FrontlineSMS: 
				<ul>
					<li>Install FrontlineSMS on your computer</li>
					<li>Make sure FrontlineSMS is running</li> 
					<li>Make sure your phone is connected to FrontlineSMS</li>
				</ul>
				
			</description>
			 
		</apex:sectionHeader>
		<apex:pageMessages id="pageMessages"/>
		<apex:pageBlock id="smsblock" title="">
			<apex:pageBlockSection id="headersection" >
				<apex:outputLabel for="membercount">Member Count</apex:outputLabel><apex:outputText id="membercount" value="{!memberCount}"/>
				<apex:outputLabel for="numbersent">Number Sent</apex:outputLabel><apex:outputText id="numbersent" value="{!numberSent}"/>
				<apex:inputField value="{!dummyTask.Description}"/>
				
			</apex:pageBlockSection>
			<apex:pageBlockSection id="smssection" rendered="{!showFrontlineSMSiframe}">
				<apex:outputLabel for="endpoint1">SMS 1 (debug)</apex:outputLabel><apex:outputText id="endpoint1" value="{!endpoint1}"/>
				<apex:outputLabel for="endpoint2">SMS 2 (debug)</apex:outputLabel><apex:outputText id="endpoint2" value="{!endpoint2}"/>
				<apex:outputLabel for="endpoint3">SMS 3 (debug)</apex:outputLabel><apex:outputText id="endpoint3" value="{!endpoint3}"/>
				<apex:outputLabel for="endpoint4">SMS 4 (debug)</apex:outputLabel><apex:outputText id="endpoint4" value="{!endpoint4}"/>
				<apex:outputLabel for="endpoint5">SMS 5 (debug)</apex:outputLabel><apex:outputText id="endpoint5" value="{!endpoint5}"/>
				<apex:outputLabel for="endpoint6">SMS 6 (debug)</apex:outputLabel><apex:outputText id="endpoint6" value="{!endpoint6}"/>
				<apex:outputLabel for="endpoint7">SMS 7 (debug)</apex:outputLabel><apex:outputText id="endpoint7" value="{!endpoint7}"/>
				<apex:outputLabel for="endpoint8">SMS 8 (debug)</apex:outputLabel><apex:outputText id="endpoint8" value="{!endpoint8}"/>
				<apex:outputLabel for="endpoint9">SMS 9 (debug)</apex:outputLabel><apex:outputText id="endpoint9" value="{!endpoint9}"/>
				<apex:outputLabel for="endpoint10">SMS 10 (debug)</apex:outputLabel><apex:outputText id="endpoint10" value="{!endpoint10}"/>
			</apex:pageBlockSection>
			<apex:pageBlockButtons id="smsbuttons">
				<apex:commandButton value="Send SMS" action="{!sendSMS}" rerender="smsblock,smsiframe,actionblock"/>
			</apex:pageBlockButtons>
		</apex:pageBlock>
		<apex:pageBlock id="actionblock" title="">
			<apex:actionPoller action="{!sendSMSToMembers}" rerender="smsblock,smsiframe,actionblock" interval="5" rendered="{!showActionBlock}"/> 
		</apex:pageBlock>
		<apex:pageBlock id="smsiframe" title="" >		
			<apex:pageBlockSection id="smsisection" rendered="{!showFrontlineSMSiframe}">
				SMS 1 (debug)
				<apex:iframe id="frontlinesmsIframe1" height="30" src="{!endpoint1}" scrolling="no"  /> 
				SMS 2 (debug) 
				<apex:iframe id="frontlinesmsIframe2" height="30" src="{!endpoint2}" scrolling="no"  /> 
				SMS 3 (debug)
				<apex:iframe id="frontlinesmsIframe3" height="30" src="{!endpoint3}" scrolling="no"  /> 
				SMS 4 (debug)
				<apex:iframe id="frontlinesmsIframe4" height="30" src="{!endpoint4}" scrolling="no"  /> 
				SMS 5 (debug)
				<apex:iframe id="frontlinesmsIframe5" height="30" src="{!endpoint5}" scrolling="no"  /> 
				SMS 6 (debug)
				<apex:iframe id="frontlinesmsIframe6" height="30" src="{!endpoint6}" scrolling="no"  /> 
				SMS 7 (debug)
				<apex:iframe id="frontlinesmsIframe7" height="30" src="{!endpoint7}" scrolling="no"  /> 
				SMS 8 (debug)
				<apex:iframe id="frontlinesmsIframe8" height="30" src="{!endpoint8}" scrolling="no"  /> 
				SMS 9 (debug)
				<apex:iframe id="frontlinesmsIframe9" height="30" src="{!endpoint9}" scrolling="no"  /> 
				SMS 10 (debug)
				<apex:iframe id="frontlinesmsIframe10" height="30" src="{!endpoint10}" scrolling="no"  /> 
			</apex:pageBlockSection>
		</apex:pageBlock>
	</apex:form>
</apex:page>

Apex controller for campaign sends:

public with sharing class extSendSMSCampaign {
	public Campaign campaign {get;set;}
	public Task dummyTask {get;set;} 
	public String SMSmessage {get;set;}
	public String endpoint1 {get;set;}
	public String endpoint2 {get;set;}
	public String endpoint3 {get;set;}
	public String endpoint4 {get;set;}
	public String endpoint5 {get;set;}
	public String endpoint6 {get;set;}
	public String endpoint7 {get;set;}
	public String endpoint8 {get;set;}
	public String endpoint9 {get;set;}
	public String endpoint10 {get;set;}
	public Integer memberCount {get;set;}
	public Integer numberSent {get;set;}
	private List<String> endpoints = new List<String>();
	private List<Task> SMSTasks = new List<Task>();
	private Id whoId;
	public Boolean showActionBlock {get;set;}
	public Boolean showFrontlineSMSiframe {get;set;}
	private Contact contact;
	private Lead lead;
	private CampaignMember campaignMember;
	
	public extSendSMSCampaign(ApexPages.StandardController stdController) {
		this.campaign = (campaign)stdController.getRecord();
		memberCount = [select count() from CampaignMember where CampaignId=:this.campaign.id AND status='sent' and (Contact.MobilePhone <> null OR Lead.MobilePhone <> null)];
		showFrontlineSMSiframe = false;
		showActionBlock=false;
		SMSmessage = 'test message';
		numberSent =0;
		dummyTask = new Task();
    }
    
    public PageReference sendSMS(){
    	showActionBlock = true;
    	showFrontlineSMSiframe = true;
    	return null;
    }
    
     public PageReference sendSMSToMembers(){ 
     	endpoints = new List<String>();
     	endpoint1 = '';
		endpoint2 = '';
		endpoint3 = '';
		endpoint4 = '';
		endpoint5 = '';
		endpoint6 = '';
		endpoint7 = '';
		endpoint8 = '';
		endpoint9 = '';
		endpoint10 = '';
		SMSTasks = new List<Task>();
    	List<CampaignMember> myCampaignMembers = new List<CampaignMember>();
    	myCampaignMembers = [select id, Contact.MobilePhone,Contact.Id,Lead.Id, Lead.MobilePhone, status from CampaignMember where CampaignId=:this.campaign.id AND status='sent' and (Contact.MobilePhone <> null OR Lead.MobilePhone <> null) limit 10];
		if(myCampaignMembers.size()>0){
	    	for(Integer i=0;i<myCampaignMembers.size();i++){
	    		CampaignMember thisMember = myCampaignMembers[i];
		    	String strippedMobilePhone = '';
		    	if(thisMember.Contact.MobilePhone!=null){
		    		strippedMobilePhone = thisMember.contact.mobilePhone;
		    		whoId = thisMember.Contact.Id;
		    	} else if(thisMember.Lead.MobilePhone!=null){
		    		strippedMobilePhone = thisMember.contact.mobilePhone;
		    		whoId = thisMember.Lead.Id;
		    	}
		    	strippedMobilePhone = strippedMobilePhone.replaceAll('[^0-9]', '');
	
		    	endpoints.add('http://localhost:8565/'+ strippedMobilePhone +'/'+ dummyTask.Description);	  
		    	SMSTasks.add(new Task(WhoId = whoId,WhatId=this.campaign.id,type = 'SMS',ActivityDate = Date.Today(),Description=dummyTask.Description,Status='Completed',Subject='SMS: '+dummyTask.Description));  	
	    	}
	    	numberSent += myCampaignMembers.size();
	    	if(endpoints.size()>0){
	    		While(endpoints.size()<10){
	    			endpoints.add('');
	    		}
		    	
		    	endpoint1 = endpoints[0];
				endpoint2 = endpoints[1];
				endpoint3 = endpoints[2];
				endpoint4 = endpoints[3];
				endpoint5 = endpoints[4];
				endpoint6 = endpoints[5];
				endpoint7 = endpoints[6];
				endpoint8 = endpoints[7];
				endpoint9 = endpoints[8];
				endpoint10 = endpoints[9];
	    	}
	    	for(CampaignMember myMember : myCampaignMembers){
	    		myMember.status='Responded';
	    	}
	    	update myCampaignMembers;
	    	insert SMSTasks;
		} else {
			showActionBlock = false;
		}
		return null;
    }
   
}