Answering Machine Detection - Ruby

Overview

This guide utilizes the Answering Machine Detection feature to determine whether a human or voicemail machine has answered the call. This allows your program to determine whether to dial a number to connect someone to the human or leave a message for the voicemail box.

When a call is initiated with machine_detection set to DetectMessageEnd, there is a parameter posted to the webhook called AnsweredBy. The potential options here are machine_end_other, machine_end_beep, machine_end_silence, and human. If the parameter returned is anything other than human, this example will take a short pause to wait for the beep and then play a message for the intended recipient. If the parameter returned is human, it will dial a number to connect the human to another person.

What do I need to run this code?

View the full code on our Github here!

You will need the Ruby SignalWire SDK and the Sinatra framework.

You will need a SignalWire phone number as well as your API Credentials (API Token, Space URL, and Project ID) which can all be found in an easily copyable format within the API tab of your SignalWire portal.

How to Run Application

Build and run on Docker

Copy env.example to .env and edit the file, adding the necessary SignalWire credentials and other phone numbers you will need.

Build the image with docker build . -t rubyamd, then run the script using docker run -it --rm -p 8080:8080 --name rubyamd --env-file .env rubyamd.

Build and run natively

You may need to use an SSH tunnel for testing this code if running on your local machine. – we recommend ngrok. You can learn more about how to use ngrok here.

Step by Step Code Walkthrough

Within the Github repository you will find 5 files:

  • Dockerfile
  • Gemfile
  • Gemfile.lock
  • README.md
  • app.rb
  • .env

We will start with the .env file that contains our environment variables, go through the app.rb file next, and finish off by showing a few different ways to trigger an outbound call in this scenario with answering machine detection enabled.

Setup your .env file

We will start by filling out our .env file so that this file contains all that we need for authentication as well as the to/from numbers and the app domain (your ngrok tunnel or server URL).

SIGNALWIRE_PROJECT_KEY=YOURKEY
SIGNALWIRE_TOKEN=YOURTOKEN
SIGNALWIRE_SPACE=YOURSPACENAME.signalwire.com
FROM_NUMBER=+15556677888
AGENT_NUMBER=+12027621401
APP_DOMAIN=https://YOURTUNNEL.ngrok.io

Walk through app.rb

We start by creating a route for Sinatra and initializing response as a VoiceResponse object, which is required in order to use Say, Dial, Hangup, and Pause.

post '/start' do
  response = Signalwire::Sdk::VoiceResponse.new

Now we use a switch statement to designate that we will take different actions depending on the value of [:AnsweredBy]. You can see below that if anything other than human is returned, this code will output "It's a machine", take a brief pause to allow the voicemail system time to reach the recording beep, leave a voicemail for the intended recipient, and hang up. If the value returned is human, this code will output "We got ourselves a live human here!" and then dial the number of your agent (or the next part of your standard call flow). In the dial, you have the option to either hard code the number you would like to dial or use environment variables.

case params[:AnsweredBy]
  when 'machine_end_other', 'machine_end_silence', 'machine_end_beep'
    puts "It's a machine!"
    # put in a little pause to allow the recording to start on the other end
    response.pause(length: 1)
    # replace messsage with whatever voicemail you want to leave
    response.say(message: "Hello! This is the County Medical Center. We are calling you to confirm your doctor appointment. Please call us back as soon as possible.")
    response.hangup
  when 'human'
    puts "We got ourselves a live human here!"
    response.dial do |dial|
      # defaulting to calling a time voice announcement number as an example
      dial.number(ENV.fetch('AGENT_NUMBER', '+12027621401'))
    end
  end

When [:AnsweredBy] returns any value other than human, the XML code is a simple <Say>.

<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say>Hello! This is the County Medical Center. We are calling you to confirm your doctor appointment. Please call us back as soon as possible.</Say>
</Response>

When [:AnsweredBy] returns human, the XML code consists of a <Dial>.

<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Dial>
    <Number>
      202-762-1401
    </Number>
  </Dial>
</Response>

Triggering calls

There are a number of different ways that you could trigger your outbound call, but here are two examples using Ruby and cURL. The most important takeaway from these is that no matter how you initiate your call, you must have machine_detection set to DetectMessageEnd. There is another parameter option enable, but that will not work in the same way. The parameter enable would work if you wanted to do some action for a human and hang up if the answering party is a machine, however, it will not allow you to take specific actions in both scenarios.

You will need your SignalWire Project ID, auth token, and space URL. You can easily find all three values in a copyable format in your SignalWire Dashboard by clicking the API tab on the lefthand sidebar.

As the above code is written in Ruby, you can always stay consistent and initiate the call using the SignalWire Ruby SDK. You can either set environment variables on your computer, or you can replace the ENV variables with your Project ID, auth token, and space URL.

client = Signalwire::REST::Client.new ENV['SIGNALWIRE_PROJECT_KEY'], ENV['SIGNALWIRE_TOKEN'], signalwire_space_url: ENV['SIGNALWIRE_SPACE']

  call = client.calls.create(
    url: ENV['APP_DOMAIN'] + '/start',
    to: params[:to],
    from: ENV['FROM_NUMBER'],
    machine_detection: "DetectMessageEnd"
  )

You can also use cURL on your command prompt or with a tool such as postman to send HTTP requests to create the call. You will need to replace the ProjectID and Space URL within the cURL URL, as well as the webhook pointing to this code, the To number, the From number, and the authentication at the bottom.

curl https://YOURSPACE.signalwire.com/api/laml/2010-04-01/Accounts/YOUR-PROJECT-ID/Calls.json \
  -X POST \
  --data-urlencode "Url=YOUR-WEBHOOK-URL" \
  --data-urlencode "To=DESTINATION-NUMBER" \
  --data-urlencode "From=CALLER-ID-FROM-SIGNALWIRE-ACCOUNT" \
  --data-urlencode 'MachineDetection=DetectMessageEnd' \
  -u "YOUR-PROJECT-ID:YOUR-TOKEN"

Wrap Up

If you are sending outbound calls, there is a high likelihood that you may hit the voicemail inbox of your intended callers. Utilizing AMD allows you to plan for that scenario and execute different instructions when a voicemail box picks up than when a person picks up the phone!

Required Resources:

Sign Up Here

If you would like to test this example out, you can create a SignalWire account and space here.

Please feel free to reach out to us on our Community Slack or create a Support ticket if you need guidance!


What’s Next

Check out the full code on our SignalWire Github Repo!

Did this page help you?