Salesforce sending SMS through FrontlineSMS

Last Updated on Thursday, 27 May 2010 07:15 Written by Steve Thursday, 27 May 2010 07:13

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;
    }

}
Learn More

Send To Omnifocus from Google Reader

Last Updated on Tuesday, 25 May 2010 11:45 Written by Steve Tuesday, 25 May 2010 11:45

When I’m surfing the web or reading email, I often run across things that I don’t have time to complete now, but want to do later. It’s at times like these I “send” things to Omnifocus. I do this in many ways. Omnifocus has great built in scripting, so I have keyboard shortcuts to send things to the Omnifocus Quick Entry form from Entourage, Safari, Mailplane, etc.

When I’m on a web page, I use Nik’s URIHandler to send things directly to my reading list via a bookmarklet. It’s incredibly handy for all sorts of things–check out my example for creating a grocery list.

But most of the stuff I read comes in via Google Reader and there hasn’t been a good way for me to send a story from Google Reader to Omnifocus. A while back Google Reader added a Send To function allowing you to send information from the current story to any URL. The problem is, Omnifocus is a locally installed app–it doesn’t have URLs.

To solve this problem, I have built a very simple PHP page that Google Reader can use to Send To Omnifocus. Here’s a quick video of how it works:

First you need to install Nik’s URIHandler. It’s really easy to install and once you’ve done that, your computer will have a way to create items in Omnifocus via Javascript.

Then you need to set up Google Reader to Send To the URIHandler.

Go to Settings > Send To and create a new link

The URL looks like this (substitute your real project and context names): http://gokubi.com/ofocus.php?title=Read: ${title}&url=${url}&context=yourContextHere&project=yourProjectHere

And the image URL looks like this: http://gokubi.com/images/omnifocus-128-75×75.png

Save that and you can Send To Omnifocus right from Google Reader!

If you want to do this on your own, and not use my send to URL, the PHP script is really simple:

<?php
echo "<script language=\"JavaScript\">\n";
echo "window.location = \"x-omnifocus://parsetasks?text=" .  htmlentities($_GET['title']) . " :: " . htmlentities($_GET['project']) . " @ " . htmlentities($_GET['context']) . " // " . htmlentities($_GET['url']) . "\";\n";
echo "</script>\n";
?>

You can create as many Send To buttons as you want–put whatever you want in the title, and use different projects and contexts. It’s very flexible and easy to use.

Learn More

Strategic impact and making multi-level relationships look nice

Last Updated on Wednesday, 5 May 2010 02:08 Written by Steve Wednesday, 5 May 2010 02:03

I’ve been doing a little prototyping in Salesforce.com as a volunteer project for a nonprofit in town. They’ve got an interesting need to track their strategic plans in Salesforce. I’ve seen this kind of thing built many times and have built it myself for another org or two. For this org they’ve got 3 levels of custom objects to hold their data. Driver is their high-level strategic focus. Goal is the next level down–there can be many goals for each Driver. And Metrics our measurable things related to each Goal.

Salesforce lets you create these objects and relationships between them very easily. I created the schema in just a few minutes. I created three objects and related them together–in this case I made them master-detail relationships. You can see in the diagram what my schema ended up looking like.

Now tracking your strategic goals is great, and most nonprofits do that. But where I think it gets more interesting is when we can tie the actual work we do directly to the metrics we’re trying to deliver on. So you’ll see in the diagram that there is a 4th object called project. Project is used to track all the work efforts of the org that are worth recording. I created a lookup relationship from Project to Metric so every project this groups takes on can be explicitly related to a Metric we need to deliver on. Which is in turn related to a Goal, which is related to a Driver. So, why are you updating that website for the 100th time this year? Because the website Project is meant to deliver on the Metric of increasing engagement of our supporters 25%, which is part of a Goal to grow the direct impact our supporters have in the world, which in turn feeds our Driver which calls for us to lead the world with a model of catalytic impact.

Those strategic objectives are made up for this example, but you get the idea how daily work can roll up to why your organization exists. Seeing that direct relationship can be very powerful. So when you look at the Project record in Salesforce, what do you see? Because there is only a relationship to the Metric object, you only see the Metric on the page layout.

Because the Project isn’t directly related to Goal or Driver, we can’t have lookup fields to those objects. Lookups are only for direct relationships, from child to parent, not up to grandparent and beyond. So we need another solution, and it turns out to be formula fields. On the Project I can create a Goal field and a Driver field that are formula fields of type Text. Here’s the formula for each of them:

for Goal: HYPERLINK("/" + Metric__r.Goal__r.Id , Metric__r.Goal__r.Name,"_self" )
for Driver: HYPERLINK( "/" + Metric__r.Goal__r.Driver__r.Id , Metric__r.Goal__r.Driver__r.Name,"_self" )

We’re creating a hyperlink to fake a real lookup. The URL of the hyperlink is relative, so it starts with / rather than http://. It then has the Id of the object, which we get by traversing up the relationships in Salesforce. From project we follow the Metric__r relationship to the Metric object, then up the Goal__r relationship to the Goal where we can grab any fields we want. With Driver we do the same thing, just go up one more level. If you think this looks hard, don’t worry, the formula field editor is really easy to use and does all the hard work for you–you just click the relationship you want to traverse.

The text of the link is the Name of the object in question. And then we target the hyperlink to “_self” so that it will load the record in question into the current window–just like a regular lookup would.

After adding these formula fields to the page layout, the end result is this:

Now we’re telling the full story about this project with a little help from simple formula fields.

Learn More
Copyright © 2009 Afterburner - Free GPL Template. All Rights Reserved.
WordPress is Free Software released under the GNU/GPL License.