In a moment of outrage with UK mobile networks, I recently released a small app that you could use to send and receive Tweets using SMS and Twilio. In the next couple of posts I want to dive into the code and show you how I built it in only 62 lines of Ruby. We’ll cover setting the application up, webhooks and sending Tweets in this post. Then in part 3 we’ll see the Twitter streaming API and Ruby’s eventmachine working together to send notifications from Twitter.
If you want to build this on your local machine you’re going to need your Twilio details and a Twitter application. You can find all the instructions on how to set them up correctly in part 1. If you haven’t already got those bits go grab them now.
If you want to follow along with the code instead of building it yourself, you can find it all on GitHub.
Got everything? Great, let’s see what’s going on.
I’ll start with a confession: when I said the whole app only took 62 lines of Ruby I might have skipped over the supporting parts of the app. We’ll need to create a Gemfile for our dependencies:
$ touch Gemfile
Then add the following to it:
# Gemfile source "https://rubygems.org" ruby "2.2.2" gem "sinatra" gem "rack" gem "twitter" gem "thin" gem "twilio-ruby" group :development do gem "envyable" end
I’m using Ruby 2.2.2, the latest version. The app is then built with Sinatra as our application framework. Rack underpins Sinatra as an interface between the web server and the framework. The Twitter and twilio-ruby gems are for interacting with their respective APIs. Thin is our web server, I’ll discuss why I chose Thin in a later post. Finally, in development I have envyable for loading config into the environment. Install all of this with:
$ bundle install
Speaking of envyable, we need to setup a config file for all those Twitter and Twilio credentials that we collected in part 1.
$ mkdir config $ touch config/env.yml
Add the following to env.yml and fill in the gaps with your details.
# config/env.yml development: TWILIO_ACCOUNT_SID: TWILIO_AUTH_TOKEN: TWITTER_CONSUMER_KEY: TWITTER_CONSUMER_SECRET: TWITTER_ACCESS_TOKEN: TWITTER_ACCESS_SECRET: TWITTER_USERNAME: MY_PHONE_NUMBER:
You’ll need to format your phone number in the E.164 style. You’ll also want to surround the number in quotes so that the yaml parser doesn’t decide it’s an integer. As an example, if my number was 07799123456 (a nice fake UK mobile number) I would write:
We’ll also need a config.ru file, this will allow us to run the application using any Rack based server rather than the default WEBrick. As we saw in the Gemfile, this is because we intend to use Thin.
$ touch config.ru
Add the following:
# config.ru require "bundler" Bundler.require disable :run require "./app.rb" run Sinatra::Application
The file does some very basic things, it requires Bundler and then the dependencies listed in the Gemfile. disable :run is a Sinatra command to disable the default web server, WEBrick. We then require the application file and the last line is a Rack method to pass the application to the web server and start it.
The final part of our setup is to create the file in which we’re going to write the rest of the application.
$ touch app.rb
In the application file we need to include our config. Add these three lines to the top:
# app.rb configure :development do Bundler.require :development Envyable.load("config/env.yml", "development") end
Now let’s get on to our first feature. This shouldn’t take us long, we’re going to send a Tweet from an SMS.
Tweeting With a Text Message
What we want to achieve here is a way to send an SMS message to our Twilio number and have it post a Tweet for us. Under the hood this looks a bit like this:
You send an SMS message, which Twilio in turn sends on to your application via a POST request, known as a webhook. When the application receives that POST request it will use the Twitter API to update your status. First we build the request handler that receives the Twilio webhook and posts to Twitter:
# app.rb post "/messages" do twitter = Twitter::REST::Client.new do |config| config.consumer_key = ENV["TWITTER_CONSUMER_KEY"] config.consumer_secret = ENV["TWITTER_CONSUMER_SECRET"] config.access_token = ENV["TWITTER_ACCESS_TOKEN"] config.access_token_secret = ENV["TWITTER_ACCESS_SECRET"] end twitter.update(params["Body"]) content_type "text/xml" "<Response/>" end
When the application receives a POST request to the /messages endpoint it updates Twitter with the Body of the message. We then return a blank <Response/> tag to let Twilio know we don’t want to do anything more with this.
I’m not so happy with that though. Firstly, configuring the Twitter client is quite awkward within the method. Let’s define a method that returns a fully authenticated client:
# app.rb def twitter @twitter ||= Twitter::REST::Client.new do |config| config.consumer_key = ENV["TWITTER_CONSUMER_KEY"] config.consumer_secret = ENV["TWITTER_CONSUMER_SECRET"] config.access_token = ENV["TWITTER_ACCESS_TOKEN"] config.access_token_secret = ENV["TWITTER_ACCESS_SECRET"] end end
Now we can just call twitter.update(params["Body"]) in our /messages action and be done.
# app.rb post "/messages" do twitter.update(params["Body"]) content_type "text/xml" "<Response/>" end
Securing the Endpoint
Alright, not quite done. There’s a problem here in; if anyone finds out your Twilio number they can send messages to it and start posting to Twitter on your behalf. Let’s make sure that we are only getting messages from your number:
# app.rb post "/messages" do twitter.update(params["Body"]) if params["From"] == ENV["MY_PHONE_NUMBER"] content_type "text/xml" "<Response/>" end
The app now checks to make sure that the SMS is sent by your phone number, but what happens if someone tries to spoof that? We can actually protect the endpoint with the Rack middleware that comes with the Twilio Ruby gem. It just needs one more line of code, place this before the action:
# app.rb use Rack::TwilioWebhookAuthentication, ENV["TWILIO_AUTH_TOKEN"], "/messages"
The application will now sign requests that come to the /messages endpoint with your Twilio auth token and compare it with the signature Twilio sends in the request headers. To read more on how Twilio signs requests, check out our security documentation.
Send that Tweet!
We nearly have everything in place to send a Tweet from an SMS. We need to start our server up. That is a case of running the following on the command line:
$ bundle exec thin start -R config.ru
You should see Thin start up the application on 0.0.0.0:3000. Now we need to expose this application to the real world so that Twilio can send webhooks to it. I like using ngrok for this and all the details on how to get set up with ngrok can be found in this guide on testing webhooks locally by my colleague Kevin.
Once you have ngrok installed, run:
$ ngrok 3000
Now that is running, open up your Twilio account and edit the number you bought. Get your ngrok URL, add /messages to the end and enter it as the Request URL for messaging.
Save that and grab your phone. Type up a witty message about Tweeting by SMS, press send and open up Twitter to see the results.
We Haven’t Written Much Code Yet…
I know, right! How else did you think all this was going to fit into 62 lines? This is the end of part 2 though, we’ve seen how to set up the application, respond to webhooks and use the Twitter gem to send a Tweet in response to a webhook. We also secured that webhook so that our Tweeting is safe and only ever from a number we expect it to come from.
In the third part of this blog post series we’ll see things from the other side. We’ll be watching the Twitter API for notifications that we want to turn into SMS messages. You can read ahead to see how it’s done by checking out the code on GitHub.
In the meantime, sit back and enjoy how little code is needed to turn an SMS message into a Tweet.
- Make Outbound Phone Calls with Ruby
- Track Delivery Status of Messages in Ruby
- Video Tutorial: How to Build SMS ETA Notifications with Twilio and Ruby on Rails
- Delay API calls to Twilio with Rails, Active Job and Sidekiq
- Go celebrity spotting with the Twilio API for WhatsApp, AWS Rekognition and Ruby