let someone else build your SMS gateway

Tom's picture
Tags: 

I've written my fair share of fly-by-night SMS services. Not for clients, of course — when someone needs a mobile campaign we talk to folks who live and breath text messaging. But I've hacked together a few hobby SMS apps on the cheap. There was this one, for example, which used email-to-SMS gateway functionality to provide a rating system for SXSW panels. Before that was LastCall, a genuine (if not shortcoded) SMS app I wrote for DCist that allowed users to check subway times, query OpenTable for available reservations and perform a number of other questionably-useful functions.

The DCist mobile service was a nightmare to put together. I bought a cracked-screen cellphone off Ebay, an unlimited-SMS mobile account and went through a half-dozen USB-to-serial cables before finding one with a Linux-compatible chipset. The software I used to spool messages from the phone to the server was a bit flaky, requiring a ton of configuration. And then WMATA changed their website and something went screwy, sending tons of unwanted text messages to hapless subscribers. The whole effort took months. The moral was clear: rolling your own SMS gateway is a huge pain in the butt.

That's why I used the email-to-SMS approach for the SXSW service. It worked beautifully, but everyone knows it's a little unprofessional — the mobile carriers can always shut you down, and users get confused when asked to send a text message to an email address.

But now we have Twitter. They own a shortcode, they pay for SMSes, and they've got a full-featured API that they'd love for you to use. Plenty of folks are using it for interesting applications — the idea of a query/response bot that runs on Twitter is hardly original, but I thought it'd be a good exercise with which to get my feet wet with Ruby and the Twitter API.

I was pleasantly surprised to see how easy it was to get up and running. Between this and ruby's excellent mechanize gem, I think it'll take me about two days to replicate a service that took me weeks to write in Perl.

One thing to note: the Twitter API is rate-limited for authenticated queries — you get 70 per hour. That's generous enough for my purposes, but querying every 51 seconds could allow some tweets to slip through the cracks if the service somehow became incredibly, unprecedentedly popular. More to the point, periodic querying means that you have to keep track of what messages you've responded to and establish a queue of outstanding requests. That means storage, which means complexity.

Instead I opted to make the script an AIMbot. Twitter becomes responsible for queueing and tracking what messages I've received, then pushes them to me in a manner that will presumably have less latency than repeatedly querying for new messages. The only storage that'll be necessary is some dead-simple caching to ensure that I don't calculate my subway schedule responses too often (it's a relatively expensive request).

The same thing could be achieved by piping incoming DM notification emails to the script — and I may end up doing that, since Twitter's IM latency seems to be a little unpredictable. But less processing is necessary to disassemble a direct-message IM than an email, so for now I'm sticking with AIM.

At any rate, here's the shell of my Twitter direct-message bot (in this incarnation it just capitalizes your direct message and DMs it back to you). Strip out the comments and configuration directives and you're looking at a whopping 15 lines of code. Not bad, right?

require 'rubygems'
require 'net/toc'
require 'net/http'
require 'uri'

class BotConfig
	AIM_screen_name = 'your AIM screen name'
	AIM_password = 'your AIM password'
	TWITTER_user = 'your Twitter account'
	TWITTER_password = 'your Twitter password'
end

def process_input(sender, content)
	return content.upcase
end

# sign on to AIM, process incoming IMs
Net::TOC.new(BotConfig::AIM_screen_name, BotConfig::AIM_password) do | message, buddy |

	# strip HTML, split by line breaks
	message_lines = message.gsub(/<br\/?>/i,"\n").gsub(/<[^>]*>/,'').split("\n")

	# only continue processing IM if it's a direct message notification
	sender = message_lines[0].scan(/direct from (.*?):/i)
	if sender.length>0
		# clean up the input
		sender = sender[0][0]
		content = message_lines[1].strip
		
		# do whatever you've gotta do to create a response
		response = process_input(sender, content)
	
		# send back a direct tweet
		response = Net::HTTP.post_form(URI.parse("http://#{BotConfig::TWITTER_user}:#{BotConfig::TWITTER_password}@twitter.com/direct_messages/new.json"), {'user' => sender, 'text' => response})	
	end

end

Reply

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <blockcode>
  • Lines and paragraphs break automatically.

More information about formatting options

Captcha
Are you a robot? We usually like robots, but not in our comments.
Copy the characters (respecting upper/lower case) from the image.