Build Call Forwarding with Twilio Programmable Voice

Communication can be a powerful force for change. We’ve seen civic engagement rise as tools for civic engagement become increasingly available to an internet-connected and mobile society. In November of 2016, Emily Ellsworth shared some tips and tricks for getting your Congressperson’s attention with one big takeaway: calling works.

This tutorial and accompanying application use Twilio to connect incoming phone calls to other phone numbers based on where the caller lives.  When a user dials in, we look up some information based on their assumed location and trigger actions inside our application.

Your business might use this functionality to automatically route your customers to a regional office or to direct callers to a survey after their interaction with customer service. Our sample application connects callers to the offices of their U.S. senators.

Loading Code Samples...
Language
const models  = require('../models');
const express = require('express');
const router  = express.Router();
const twilio = require('twilio');

// Very basic route to landing page.
router.get('/', function (req, res) {
  res.render('index');
});


// Verify or collect State information.
router.post('/callcongress/welcome', (req, res) => {
  const response = new twilio.twiml.VoiceResponse();
  const fromState = req.body.FromState;

  if (fromState) {
    const gather = response.gather({
      numDigits: 1,
      action: '/callcongress/set-state',
      method: 'POST'
    });
    gather.say("Thank you for calling congress! It looks like " +
               "you're calling from " + fromState + "." +
               "If this is correct, please press 1. Press 2 if " +
               "this is not your current state of residence.");
  } else {
    const gather = response.gather({
      numDigits: 5,
      action: '/callcongress/state-lookup',
      method: 'POST'
    });
    gather.say('Thank you for calling Call Congress! If you wish to' +
               'call your senators, please enter your 5-digit zip code.');
  }
  res.set('Content-Type', 'text/xml');
  res.send(response.toString());
});


// Look up state from given zipcode.
//
// Once state is found, redirect to call_senators for forwarding.
router.post('/callcongress/state-lookup', (req, res) => {
  zipDigits = req.body.Digits;
  // NB: We don't do any error handling for a missing/erroneous zip code
  // in this sample application. You, gentle reader, should to handle that
  // edge case before deploying this code.
  models.ZipCode.findOne({where: { zipcode: zipDigits}}).get('state').then(
    (state) => {
      return models.State.findOne({where: {name: state}}).get('id').then(
        (stateId) => {
          return res.redirect('/callcongress/call-senators/' + stateId);
        }
      );
    }
  );
});


// If our state guess is wrong, prompt user for zip code.
router.get('/callcongress/collect-zip', (req, res) => {
  const response = new twilio.twiml.VoiceResponse();
  const gather = response.gather({
      numDigits: 5,
      action: '/callcongress/state-lookup',
      method: 'POST'
    });
  gather.say('If you wish to call your senators, please ' +
              'enter your 5-digit zip code.');
  res.set('Content-Type', 'text/xml');
  res.send(response.toString());
});


// Set state for senator call list.
//
// Set user's state from confirmation or user-provided Zip.
// Redirect to call_senators route.
router.post('/callcongress/set-state', (req, res) => {
  // Get the digit pressed by the user
  const digitsProvided = req.body.Digits;

  if (digitsProvided === '1') {
    const state = req.body.CallerState;
    models.State.findOne({where: {name: state}}).get('id').then(
      (stateId) => {
        return res.redirect('/callcongress/call-senators/' + stateId);
      }
    );
  } else {
    res.redirect('/callcongress/collect-zip')
  }
});


function callSenator(req, res) {
  models.State.findOne({
    where: {
      id: req.params.state_id
    }
  }).then(
    (state) => {
      return state.getSenators().then(
        (senators) => {
          const response = new twilio.twiml.VoiceResponse();
          response.say("Connecting you to " + senators[0].name + ". " +
           "After the senator's office ends the call, you will " +
           "be re-directed to " + senators[1].name + ".");
          response.dial(senators[0].phone, {
            action: '/callcongress/call-second-senator/' + senators[1].id
          });
          res.set('Content-Type', 'text/xml');
          return res.send(response.toString());
        }
      );
    }
  );
}

// Route for connecting caller to both of their senators.
router.get('/callcongress/call-senators/:state_id', callSenator);
router.post('/callcongress/call-senators/:state_id', callSenator);


function callSecondSenator(req, res) {
  models.Senator.findOne({
    where: {
      id: req.params.senator_id
    }
  }).then(
    (senator) => {
      const response = new twilio.twiml.VoiceResponse();
      response.say("Connecting you to " + senator.name + ". ");
      response.dial(senator.phone, {
        action: '/callcongress/goodbye/'
      });
      res.set('Content-Type', 'text/xml');
      return res.send(response.toString());
    }
  );
}

// Forward the caller to their second senator.
router.get('/callcongress/call-second-senator/:senator_id',  callSecondSenator);
router.post('/callcongress/call-second-senator/:senator_id',  callSecondSenator);


// Thank user & hang up.
router.post('/callcongress/goodbye', (req, res) => {
  const response = new twilio.twiml.VoiceResponse();
  response.say("Thank you for using Call Congress! " +
               "Your voice makes a difference. Goodbye.");
  response.hangup();
  res.set('Content-Type', 'text/xml');
  res.send(response.toString());
});

module.exports = router;
Implementation of a call forwarding app in node
Welcome the caller and gather forwarding information

Implementation of a call forwarding app in node

Find your language and framework of choice below to get to the source code in your language and follow step-by-step instructions to learn how to build call forwarding for your own applications:

Need some help?

We all do sometimes; code is hard. Get help now from our support team, or lean on the wisdom of the crowd browsing the Twilio tag on Stack Overflow.

Loading Code Samples...
const models  = require('../models');
const express = require('express');
const router  = express.Router();
const twilio = require('twilio');

// Very basic route to landing page.
router.get('/', function (req, res) {
  res.render('index');
});


// Verify or collect State information.
router.post('/callcongress/welcome', (req, res) => {
  const response = new twilio.twiml.VoiceResponse();
  const fromState = req.body.FromState;

  if (fromState) {
    const gather = response.gather({
      numDigits: 1,
      action: '/callcongress/set-state',
      method: 'POST'
    });
    gather.say("Thank you for calling congress! It looks like " +
               "you're calling from " + fromState + "." +
               "If this is correct, please press 1. Press 2 if " +
               "this is not your current state of residence.");
  } else {
    const gather = response.gather({
      numDigits: 5,
      action: '/callcongress/state-lookup',
      method: 'POST'
    });
    gather.say('Thank you for calling Call Congress! If you wish to' +
               'call your senators, please enter your 5-digit zip code.');
  }
  res.set('Content-Type', 'text/xml');
  res.send(response.toString());
});


// Look up state from given zipcode.
//
// Once state is found, redirect to call_senators for forwarding.
router.post('/callcongress/state-lookup', (req, res) => {
  zipDigits = req.body.Digits;
  // NB: We don't do any error handling for a missing/erroneous zip code
  // in this sample application. You, gentle reader, should to handle that
  // edge case before deploying this code.
  models.ZipCode.findOne({where: { zipcode: zipDigits}}).get('state').then(
    (state) => {
      return models.State.findOne({where: {name: state}}).get('id').then(
        (stateId) => {
          return res.redirect('/callcongress/call-senators/' + stateId);
        }
      );
    }
  );
});


// If our state guess is wrong, prompt user for zip code.
router.get('/callcongress/collect-zip', (req, res) => {
  const response = new twilio.twiml.VoiceResponse();
  const gather = response.gather({
      numDigits: 5,
      action: '/callcongress/state-lookup',
      method: 'POST'
    });
  gather.say('If you wish to call your senators, please ' +
              'enter your 5-digit zip code.');
  res.set('Content-Type', 'text/xml');
  res.send(response.toString());
});


// Set state for senator call list.
//
// Set user's state from confirmation or user-provided Zip.
// Redirect to call_senators route.
router.post('/callcongress/set-state', (req, res) => {
  // Get the digit pressed by the user
  const digitsProvided = req.body.Digits;

  if (digitsProvided === '1') {
    const state = req.body.CallerState;
    models.State.findOne({where: {name: state}}).get('id').then(
      (stateId) => {
        return res.redirect('/callcongress/call-senators/' + stateId);
      }
    );
  } else {
    res.redirect('/callcongress/collect-zip')
  }
});


function callSenator(req, res) {
  models.State.findOne({
    where: {
      id: req.params.state_id
    }
  }).then(
    (state) => {
      return state.getSenators().then(
        (senators) => {
          const response = new twilio.twiml.VoiceResponse();
          response.say("Connecting you to " + senators[0].name + ". " +
           "After the senator's office ends the call, you will " +
           "be re-directed to " + senators[1].name + ".");
          response.dial(senators[0].phone, {
            action: '/callcongress/call-second-senator/' + senators[1].id
          });
          res.set('Content-Type', 'text/xml');
          return res.send(response.toString());
        }
      );
    }
  );
}

// Route for connecting caller to both of their senators.
router.get('/callcongress/call-senators/:state_id', callSenator);
router.post('/callcongress/call-senators/:state_id', callSenator);


function callSecondSenator(req, res) {
  models.Senator.findOne({
    where: {
      id: req.params.senator_id
    }
  }).then(
    (senator) => {
      const response = new twilio.twiml.VoiceResponse();
      response.say("Connecting you to " + senator.name + ". ");
      response.dial(senator.phone, {
        action: '/callcongress/goodbye/'
      });
      res.set('Content-Type', 'text/xml');
      return res.send(response.toString());
    }
  );
}

// Forward the caller to their second senator.
router.get('/callcongress/call-second-senator/:senator_id',  callSecondSenator);
router.post('/callcongress/call-second-senator/:senator_id',  callSecondSenator);


// Thank user & hang up.
router.post('/callcongress/goodbye', (req, res) => {
  const response = new twilio.twiml.VoiceResponse();
  response.say("Thank you for using Call Congress! " +
               "Your voice makes a difference. Goodbye.");
  response.hangup();
  res.set('Content-Type', 'text/xml');
  res.send(response.toString());
});

module.exports = router;