Coding a Rails lookup SMS app for my grandparents

December 18, 2020
Written by
Reviewed by

Coding a Rails lookup SMS app for my grandparents

#GiftOfCode is almost over but it’s never too late to participate!

You don’t have to create a heavy app with 2K handwritten lines of code to make someone happy and I’m gonna prove it to you now with a small SMS service for my grandparents.

My grandparents often have phone calls from numbers they don’t know and they often call back in the case one of the family members would have changed their phone number and not told them. But sometimes they end up paying extra fees for that.

My SMS service will use lookup functionality from Twilio with the Ekata Reverse Phone add-on to tell them if calling back a specific number may endup in extra billing.

Warning before we go: you can follow this tutorial from anywhere in the world but the Ekata Reverse Phone functionality will work as expected only in the U.S.

Requirements

To create the same application you need to have

 

Twilio Configuration

First things first, let’s subscribe to the lookup and the ekata_reverse_phone add- on in the Twilio console. Go in the ekata reverse phone page on the Twilio console and click Install, agree to the Ekata Reverse Phone terms of service and you’re all set!

While you are in the console have a look at this page and grab your Account SID and Auth Token, we will need them later.

 

Coding our gift using RubyOnRails

In your terminal create a new ruby on rails project by typing $ rails new PhoneLookup

Go in your freshly created folder with $ cd PhoneLookup and open it with your favorite text editor.

Dependencies and configuration

To create this application we will use the Twilio gem for the Twilio part and the Figaro gem to handle privacy. Open the Gemfile with your text editor and add the code below.

# Gemfile
gem 'figaro'
gem 'twilio-ruby'

In your console install the gems with $ bundle install and run $ bundle exec figaro install to run the figaro intializer. This will create a new configuration file config/application.yml and append your .gitignore to tell git not to track this new file.

This is where we are going to put all our environment variables.

In this file add the following lines and replace the X with the values you copied in your Twilio console.

# config/application.yml
twilio_account_sid: "X"
twilio_auth_token: "X"

We can now create an initializer for our Twilio Client.

Under the folder config/initializers create a new file called twilio.rb. Fill it with:

# config/initializers/twilio.rb
Twilio.configure do |config|
  config.account_sid = ENV["twilio_account_sid"]
  config.auth_token = ENV["twilio_auth_token"]
end

Now our app knows our credentials, we can invoke a Twilio client without passing those arguments each time. Since the credentials are stored in a file we don’t share on Git, our secrets are safe!

Routing

Cool, it’s time to add some webhooks to interact with Twilio.

Open config/routes.rb

Add the following routes so the file looks like

# config/routes.rb
Rails.application.routes.draw do
  post '/phone-number-lookup', action: :phone_number_lookup, controller: 'twilio'
  post 'callback-sms', action: :callback_sms, controller: 'twilio'
end

As you can see we mention in the routes a twilio controller and the phone_number_lookup and callback_sms actions but we haven't created them yet.

The controller

Let’s create the twilio_controller.rb under app/controllers. Here is a boilerplate for this file with our two functions.

# app/controllers/twilio_controller.rb
class TwilioController < ApplicationController
    skip_before_action :verify_authenticity_token

    def phone_number_lookup
    end

    def callback_sms
    end
end

I add here the skip_before_action :verify_authenticity_token line for testing purposes, but you definitely don’t want this to go to production as this will allow anyone having your website’s url to make requests to all the endpoints of the twilio_controller. The ekata_reverse_phone is billed at each request if this wasn’t convincing enough.

There are many ways to create secure webhooks endpoints in RubyOnRails. Luciano Bercerra even wrote a blog post that explains how to do that for Twilio - (kudos!), but for now our code will do.

Let’s complete these functions and start with the one we will cover least today: callback_sms. Basically this is where the status updates for our outgoing SMS arrives. Since we do not need to do anything specific here to achieve our goal I’ll give you a base and up to you to handle the callbacks your way.

A very minimalistic version of that function would be as follows. Feel free to extend it to your needs.

# app/controllers/twilio_controller.rb
def callback_sms
    # code your callback here
    render json: { message: "ok" }, status: :ok
end

The logic

There is a lot of information available in the addon response but the only one I’ll check today is if this number has warnings associated with it.

Lets create a Twilio client and do our lookup with the content of incoming SMS.

# app/controllers/twilio_controller.rb
def phone_number_lookup

# invoke Twilio Client
@client = Twilio::REST::Client.new(config.account_sid, config.auth_token)
# phone number lookup
phone_number_info = @client.lookups
                           .phone_numbers(params['Body'])
                           .fetch(add_ons: ['ekata_reverse_phone'])
end

Yay, we can now search for data. Let’s check if the number has warnings associated with it and set our answer accordingly.

# app/controllers/twilio_controller.rb

# pull data from the response
if phone_number_info.add_ons['results']['ekata_reverse_phone']['result']['warnings'].empty?
response_body = "The phone number #{params['Body']} has no associated warning."
else
repsonse.body = "The phone number #{params['Body']} has associated warning(s). Calling back may trigger additional fees."
end

And that may be it before returning our response to Twilio API, but as I mentioned above this service is not available in all countries. It can also have failures and this is a good practice to prevent even in highly unlikely cases.

As you saw looking into the response data is a bit verbose so let’s create two private functions at the bottom of our controller to keep phone_number_lookup clean.

# app/controllers/twilio_controller.rb
private

def ekata_available_in_country(ekata_reverse_phone)
    ekata_reverse_phone && ekata_reverse_phone['status'] != "failed"
end

def ekata_success(success, ekata_reverse_phone)
    success == "successful" && ekata_reverse_phone && ekata_reverse_phone['status'] == "successful"
end

Now let’s use them in phone_number_lookup to set the accurate answers to our incoming SMS and send the response to Twilio.

This is what the function now looks like:

def phone_number_lookup
  # invoke Twilio Client
  @client = Twilio::REST::Client.new(config.account_sid, config.auth_token)
  # phone number lookup
  phone_number_info = @client.lookups
                             .phone_numbers(params['Body'])
                             .fetch(add_ons: ['ekata_reverse_phone'])

  if !ekata_available_in_country(phone_number_info.add_ons['results']['ekata_reverse_phone'])
    response_body = "Sorry this service is not available in your country"
  elsif ekata_success(phone_number_info.add_ons['status'], phone_number_info.add_ons['results']['ekata_reverse_phone'])
    # pull data from response
    if phone_number_info.add_ons['results']['ekata_reverse_phone']['result']['warnings'].empty?
      response_body = "The phone number #{params['Body']} has no associated warning."
    else
      repsonse.body = "The phone number #{params['Body']} has associated warning(s). Calling back may trigger additional fees."
    end
  else
    response.body = "An error occurred. We are sorry."
  end

  response =Twilio::TwiML::MessagingResponse.new 
  response.message do |r|
    r.body response_body
  end
  render xml: response.to_xml
end

Trying our app

To finally link it to Twilio and test it, we need to have an url that supports ssl. I often use ngrok to achieve that.

In a terminal window type $ ngrok 3000 and copy the generated url. Example: https://your-url.ngrok.io. This opens a safe tunnel between localhost:3000 and the world.

To let Rails know this is okay to serve our application from our url add config.hosts << "vvenance.ngrok.io" before the closing end in config/environments/development.rb.

This will allow this only for dev purposes. You will need to do the same in config/environments/production.rb when ready for production with your own domain.

Now in another terminal window at our project root run $ rails s which will launch a local server running on localhost:3000.

In the Twilio console, in our phone number menu, under A message comes in select webhook and add https:/your-url.ngrok.io/phone-number-lookup near HTTP POST. Now save it.

Testing our phone number lookup on a mobile phone

You can send an US valid number by SMS to your own Twilio number to test it out and see that with less than 50 lines in our controller we have created a simple service anyone can use to have insights on the interest of calling back a number. Bravo!

If you want to use this code as a gift the full code is available here, otherwise you can add your - little - contribution here until the 21 of december.

Grandma and grandchild hugs

Now if you’ll excuse me I’ve got a lovely grandma to call so I’m leaving you here with a few other articles to read and my best wishes for this holiday season!

I can’t wait to see what you will offer!

Valériane Venance is a Developer Evangelist at Twilio. She loves RubyOnRails, her grandma and the snow. Leave her a message at vvenance@twilio.com or on Twitter if you’ve built something cool you’d like to share or brag about!