Using an SMS Aggregator > Example Service: Conference Messaging
Let's imagine we're organizing a large conference and wish to allow participants and organizers to communicate with one another via SMS. In the hypothetical service, participants will register with their:
Phone number
Nickname
Aggregators such as Clickatell allow you to lease a phone number with exclusive access to send and receive SMSs from that number. They also allow batch messages to be created that allow large amounts of messages to be quickly sent. So, for the purposes of our example, our service supports the following functionality:
Conference organizers can broadcast messages via a web interface.
Other participants broadcast messages ("shout") to conference participants by prefixing their messages with '!'.
Any message not prefixed by '!' will be considered feedback for the organizers and simply stored for later review.
For example, the conference organizer wants to let participants know that the party that was scheduled for the opening evening has been moved from 6:00 to 8:00 p.m. She navigates to an internal web page and types:
Every conference participant receives a message on their phone:
After learning that the time of the party has changed, one of the participants decides to organize a dinner event. He replies to the text message sent by the admin:
Our service looks for messages pre-pended with '!' and broadcasts it to every conference participant's mobile phone. Each participant will see:
To illustrate how we would build our conference messaging service using an SMS aggregator, we will use the APIs provided by Clickatell. Clickatell is one of the most accessible SMS aggregators: You pay only for the messages sent and received (i.e., there is no set-up fee) and they give you 10 free credits on sign-up for testing. You can sign-up for a free developer account, its APIs are well documented, it connects to more than 600 different carrier networks, and it offer connectivity via a wide variety of protocols (e.g., HTTP/S, SMPP, SMTP, FTP, XML, and COM).
To implement this service, you'll need a few things:
A Clickatell developer account (http://www.clickatell.com/products/sms_gateway.php)
Python 2.4 or higher (http://www.python.org/download)
A web server that can execute Python CGI scripts (most web hosting providers support this). The following files described in this section will need to be placed in the web server's root path:
config.py: Static configuration data that is unique for each project.
broadcast.py: Functions that support sending SMSs to all registered participants.
admin.html: A web form that allows the conference admins to send broadcasts to all the participants.
admin_send.py : The script that processes the form that admin.html submits. It actually sends the SMSs.
responder.py : A script that is called by Clickatell when participants send SMSs to our leased number.
NOTE
Testing the incoming SMS portion of this example requires leasing a phone line from Clickatell, which can be expensive if you're just getting your feet wet. However, you can test just the web-based broadcast (outbound only) portion of this example with a free account.
We'll be using the Clickatell HTTP/S API for implementing our service. HTTP/S is one the simplest ways to access Clickatell, but it's also very feature rich. The API Guide is available at (http://www.clickatell.com/downloads/http/Clickatell_HTTP.pdf).
Our project relies on certain configuration elements that will be unique to every person running the example. You'll need to edit this file to match your particular situation.
config.py:
#change these to match your registration info api_id = '12345678' user = 'test' passwd = 'testpasswd' reply_num = '13334445555' conference = 'TestCon' nick_number_dict = { '14445556666' : 'bob', '15556667777' : 'ted', '16667778888' : 'teresa' }
Our Clickatell set up require four pieces of configuration:
api_id: A unique ID given to you when you sign up for Clickatell's service.
user: The user name you created when you signed up for Clickatell.
passwd: The password you set up for Clickatell.
reply_num: The phone number replies will be sent to. You need to lease this number from Clickatell (explained in more detail below). If you don't want to lease a number, just leave this alone.
This example uses two other pieces of configuration data:
conference: The name of the conference we're setting up.
nick_number_dict: keeps track of all our conference participants. More advanced applications may want to use a SQL database or other more flexible data format for this.
Let's take a look at a short Python script that will use the Clickatell HTTP API to send one message. This script is just an example, and isn't one of the files you'll be installing on your web server to implement the conference messaging service. It still relies on our configuration data though.
simple_send.py:
Code View: Scroll / Show All
import urllib import config def send_message(to, text): paramdict = { 'api_id': config.api_id, 'user' : config.user, 'password' : config.passwd, 'to' : to, 'from' : config.reply_num, 'text' : text } params = urllib.urlencode(paramdict) result = urllib.urlopen("https://api.clickatell.com/http/sendmsg", params) print result.readlines() if __name__ == "__main__": #change the phone number to your own send_message('13334445555', 'test message')
The first thing this script does is import urllib and config. Python comes with urllib, it allows communication over most common Internet protocols including HTTPS, which is what we'll be using it for. We created config ourselves in the last section.
We then define a function called send_message that takes two parameters:
text: The text of the message we want to send.
to: The phone number of the person we want to send the message to.
The send message function creates a dictionary that contains all the parameters that we'll need to send to Clickatell's sendmsg function.
The script then calls urlencode (from the urllib module), which converts the dictionary into a string that is URL-encoded.
All we need to do now is send a POST request to the sendmsg command at Clickatell. We use the urlopen command to do so, passing the URL as the first parameter and the POST variables as the second.
Lastly, we print out the response so you can see what it looks like.
The simple script above can be run on any computer with python installed. The send_message function takes a phone number and a message. Change the number to your own, and the message to whatever you'd like, then invoke the script (from the command line):
or on Windows:
You could use this method to implement the conference broadcast functionality of our example service, but Clickatell provides a better mechanism.
The example above sends an individual message, but most aggregators provide a batching mechanism that will allow you to a message to a large number of people using only one API invocation. This will speed up the sending of the messages and reduce the likelihood of messages getting dropped.
Batching using Clickatell's API consists of two steps (three if you use the auth command to obtain a session ID we just pass our credentials each time):
Set up the text of the message, obtain a batch_id
Send the set up message to any number of phones by referencing the batch_id and passing a comma delimited list of phone numbers to one of the send commands (we use quicksend)
Broadcast.py implements two methods: batch_setup and broadcast.
In order to send a message to all the conference participants, broadcast is called. It calls batch_setup to set a new batch up, and then sends a message to everyone in the configuration file.
This file can be executed at the command prompt as with simple_send.py. It will send two messages to every number in your configuration file (so make sure it's accurate it should probably only have your number in it at this point).
broadcast.py:
Code View: Scroll / Show All
import urllib import re import config def batch_setup(message): batch_setup_params = { 'api_id': config.api_id, 'user' : config.user, 'password': config.passwd, 'from' : config.reply_num, # the number replies go to 'mo' : '1', # allow responses 'template' : message } batch_id_re = re.compile("ID:[ ]*([0-9a-f]+)") params = urllib.urlencode(batch_setup_params) res = urllib.urlopen( "https://api.clickatell.com/http_batch/startbatch", params ) bid = 0 # extract the batch id from the response for line in res.readlines(): print line m = batch_id_re.search(line) if m: bid = m.group(1) print "BID: %s" % bid return bid return None def broadcast(msg, shout_from = None): if shout_from: nick = config.nick_number_dict[shout_from] message = '%s(%s) says: %s' %(nick, shout_from, msg) else: message = '%s Announcement: %s' %(config.conference, msg) bid = batch_setup(message) if not bid: raise Exception("Error Getting Batch ID") # get a comma-delimited list of numbers from our dictionary to_nums = ','.join(config.nick_number_dict.keys()) batch_send_params = { 'api_id': config.api_id, 'user' : config.user, 'password' : config.passwd, 'batch_id' : bid, 'to' : to_nums } params = urllib.urlencode(batch_send_params) res=urllib.urlopen( "https://api.clickatell.com/http_batch/quicksend", params ) print res.readlines() # simple test if we run the file directly # it will send a message to all the numbers # in conf.py if __name__ == "__main__": broadcast('test message', '12063497060') broadcast('test message')
Our function broadcast takes two parameters:
msg: The text of the message we wish to broadcast.
shout_from: The phone number of the person shouting. The default (None) will send an official announcement.
The first thing broadcast does is create the text of the message based on the contents of the shout_from parameter. It then calls the batch_setup function, passing it the message we wish to send. The command is set up by submitting a POST with the following parameters to (https://api.clickatell.com/http_batch/startbatch).
api_id, user, password: Clickatell configuration parameters described previously.
from: The phone number replies will be sent to. Important note: all phone numbers should be specified in international format (i.e., beginning with country code).
mo: Indicates whether the receiver can send replies to the message (remember, "mo" means "mobile originated"). Set to one for true, zero for false.
template: The message you wish to send. This will have to be URL encoded to be passed as a parameter.
The code calls this URL from batch_setup in the way that it called sendmsg in our first example. Clickatell replies with a batch_id that will be used to actually send our message. Our code uses a regular expression to parse out the batch_id and returns it to the user.
Once broadcast has retrieved the batch_id, it takes all of the phone numbers listed in our configuration file and joins them together into one long string, with each number separated by commas.
We then use urlopen to POST with the following parameters to (https://api.clickatell.com/http_batch/quicksend).
api_id, user, password: Clickatell configuration parameters described previously.
batch_id: The batch_id you obtained as a response to your startbatch command above.
to: A comma-delimited list of numbers to send the message you configured as template in the startbatch command.
And with that, your broadcast message is on its way.
The sample code prints out the responses to each message so you can see what they look like. Feel free to remove or disable the print statements as you wish.
Our sample doesn't track the messages once they are sent, but check whether your messages were successful by examining the response to your quicksend POST.
The response will contain one line for each number you send the message to. Each line will contain an apimsgid and the phone number of the recipient:
['ID: cc959204830b71ed27a014c3622356ee0 To: 12223334444\n']
You can then use the querymsg command to get the status of the particular recipient/message combination that the api_id refers to:
Code View: Scroll / Show All
http://api.clickatell.com/http/querymsg?user=xxxx&password=xxxx&api_id=xxxx& apimsgid=xxxxx
NOTE
Clickatell's quicksend command supports a maximum of 100 numbers on the To: line if submitting the request via HTTP GET, or 300 if submitting the request via HTTP POST. Break your request into multiple calls if you are sending to more numbers than these.
Our web frontend consists of three pieces. The first is a static HTML page that presents a simple form to the conference administrator. The second is a CGI script that processes the form submission. The third is a CGI script that will be used as a callback by Clickatell when they receive inbound SMSs for your leased phone number.
The interface for the conference administrator is presented by one simple HTML form.
admin.html:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html lang="en"> <head> <title> Conference Announcement System </title> </head> <body> <h3> Send an Announcement </h3> <form action="admin_send.py" method="post"> <p> Message: <input type="text" name="message"/> </p> <p> <input type="submit" value="Announce"/> </p> </form> </body> </html>
It has one field message that should be filled in with the text of the message that should be sent. The form is submitted via POST to the CGI script admin_send.py.
The second part of our web frontend is a python CGI script that processes the form described in admin.html. It imports the broadcast functionality we wrote earlier and uses it to send a message to everyone.
admin_send.py:
import sys import cgi import cgitb; cgitb.enable() from broadcast import * def result(text): print "Content-type: text/html\n\n" print """ <html> <head><title>Announcement Made</title></head> <body>%s</body> </html> """ % text sys.exit(0) form = cgi.FieldStorage() message = form.getfirst("message") broadcast(message) result("%s sent!" % message)
First the script imports several needed libraries and the broadcast functionality we wrote earlier. The cgitb.enable() command enables tracebacks on the web form, which makes it much easier to debug any potential problems.
We then define a function result() that will return a status page to the user.
The next line after the function definition uses the cgi module to fetch the parameters that were passed to our script.
We grab the message parameter (you'll recall that's the name of the field from admin.html) and then we pass it to broadcast.
Finally, we call our result function which returns a page to the user that confirms that he's sent the message.
NOTE
This short cut assumes you have access to a web hosting service that is running Apache or other server that will let you run python CGI scripts.
The easiest way to get everything working is to put all the files in the same directory. Everything with a .py extension will need to have correct permissions. This is usually 711 (rwx..x..x) on Unix systems, though the correct permissions can vary from host to host. For more advanced applications, you'll want to separate your static pages from your scripts or follow other practices designed to prevent source code compromise.
There are many other Python web frameworks that can be used, but they are not necessarily as straightforward or widely supported as Apache and pure CGI. Read about them at http://wiki.python.org/moin/WebFrameworks.
To receive messages using Clickatell, you will need to set up two-way messaging, which involves leasing a phone number and setting up a callback URL. When an SMS is received to our leased number, Clickatell will invoke our callback URL with parameters containing the message details.
The Clickatell guide for setting up two-way messaging is at https://www.clickatell.com/downloads/Clickatell_Two_Way_Technical_Guide.pdf.
NOTE
As a quick reference, here is Clickatell's pricing for two-way messaging as of May 2007:
Number type | Length | Once-off set up fee (in euros) | Monthly rental (in euros) |
Standard number | Up to 12 digits | 100 | 25; 75 |
Tagged number | 16 digits | 50 | 15 |
Tagged number range | 1 digit (10 tagged numbers) | 100 | 30 |
2 digits (100 tagged numbers) | 100 | 60 | |
3 digits (1,000 tagged numbers) | 100 | 80 | |
4 digits (10,000 tagged numbers) | 100 | 90 |
The URL for this script needs to be accessible to Clickatell (so you'll need a static IP address or domain). Place it in the same directory as the rest of the scripts we've created. Our callback URL will receive a post from Clickatell with two parameters: text (the body of the message) and from (the number the message was received from).
responder.py:
import sys import cgi import cgitb; cgitb.enable() import re from broadcast import * form = cgi.FieldStorage() text = form.getfirst("text") #message is in the 'text' variable from_pn = form.getfirst("from") response_file = file("responses.txt", "a") #Check if message is of the form "!message body" m = re.match("!(.*)", text) if m: #m.group(1) is the text of the shout broadcast(m.group(1), from_pn) else: #if we didn't get a 'shout', write the response into a file response_file.write("%s : %s\n" % (from_pn, text)) response_file.close()
First, we import the standard raft of libraries we'll be using, including our own.
Next, we grab the parameters via the cgi.FieldStorage object. We pick out the text and from parameters
We open a file responses.txt in append mode. Append mode ensures that we will add new lines to the file every time we write to it, instead of overwriting it. This file path should be changed to be outside the web path to prevent unauthorized users from viewing it. This file will be used to store feedback messages (i.e., those not prepended with a '!').
The re module is used to create a regular expression that will match text that has a '!' as the first character and will group everything else for later retrieval. We use the regex to try to match the message we were sent.
For instance, this will match the regex:
This won't:
If there was a match, we call broadcast and pass it the text (not including the '!') and the number we received the request from.
If there isn't a match, we write the text out to the file we opened earlier.
NOTE
If you change the script you may need to debug it. An easy way to debug the script is to call it from a web browser and supply your own text and from parameters.
Another way to test and debug the script from the command line, without a web server, is to invoke python with a URL-encoded string of form parameters as the second parameter:
python responder.py text='!what%20is%20up'\&from=13334445555
The escape character \ before the & is necessary on many Unix shells to prevent the shell from interpreting the ampersand. The same goes for the ticks around the text parameter '!' is a special character in most shells. Windows and some Unix shells will be different.
Install the files onto your hosting account as described earlier, open the admin page, and send a message.
Make sure you register your callback URL and try sending a shout. Or, try using the debugging methods above to test the shouting functionality if you don't want to lease a phone number.
Good work, you've just built a send and receive broadcast message service using an SMS aggregator! Now let's look at some other methods for implementing an SMS service.