Get Started

Voicemail

Voicemail is a telephony application with which everyone is familiar. Twilio makes building a voicemail system simple, and makes integrating it with the web or other messaging technologies just as straight forward. This recipe will describe the building of a simple voicemail box application.

A voicemail system consists of two applications. One application allows people to record messages and leave them in a mailbox. The other application allows the owner of the mailbox to listen to and manage the messages he has received. This system assigns each mailbox an extension and a passcode and has two entry points, one for leaving messages and one for checking them. If you were to integrate this into your own system or the Company Directory recipe, you would most likely wire the entry points into your own application, bypassing the step where you ask the caller for their mailbox extension.

This demo utilizes the Twilio Response Library to facilitate TwiML creation. If you haven't already, visit the Response Library introduction to familiarize yourself. Make sure to download a copy of the PHP library.

Download

voicemail.zip

Table of Contents

Create a Voicemail Database

We first provide a HTML form to the user containing a field for the user's phone number.

  • howtos/voicemail/voicemail.sql
    CREATE TABLE voicemailbox (
    vmb_extension varchar(8) primary key not null, 
    vmb_description varchar(32) not null, 
    vmb_passcode varchar(8) not null,
    vmb_last_checked datetime
    );
    
    insert into voicemailbox (
    	vmb_extension,
    	vmb_description,
    	vmb_passcode
    )values (
    	'1234',
    	'Test voicemail',
    	'9411'
    );
    
    CREATE TABLE messages (
    	message_id int not null primary key auto_increment, 
    	message_frn_vmb_extension varchar(8) not null, 
    	message_date datetime not null, 
    	message_from varchar(16),
    	message_audio_url varchar(1024),
    	message_flag int(1) default 0
    );    

Next we'll need to define our data access functions. We'll need a couple functions here: a function for adding a new voicemail to the messages table, functions for retrieving messages, and a function for updating the voicemail flag (new,saved,deleted).

  • howtos/voicemail/messages.php
    <?php
    
    //DB Constants - Change to your settings
    $db_host='localhost';
    $db_name='company_directory';
    $db_user='db_username';
    $db_passwd='db_password';
    
    //function for retrieving voicemail box by exten
    function getMailbox($voicemail_exten) {
    	global $db_name, $db_host,$db_user,$db_passwd;
    
    
    	mysql_connect($db_host, $db_user, $db_passwd)
    		or die('Could not connect: ' . mysql_error());
    
    	mysql_select_db($db_name) or die('Could not select database');
    
    
    	//make sure inputs are db safe
    	$voicemail_exten = mysql_real_escape_string($voicemail_exten);
    
    
    	// Performing SQL query
    	$query = sprintf("select * from voicemailbox where vmb_extension='%s'",
    		$voicemail_exten);
    
    	$result = mysql_query($query) or die('Query failed: ' . mysql_error());
    
    	$mailbox = false;
    
    	if ($line = mysql_fetch_array($result, MYSQL_ASSOC)) {
    		$mailbox = array();
    		$mailbox['exten'] = $line['vmb_extension'];
    		$mailbox['desc'] = $line['vmb_description'];
    		$mailbox['passcode'] = $line['vmb_passcode'];
    	}
    
    	mysql_close();
    	return $mailbox;
    }
    
    function addMessage($voicemail_exten, $caller_id, $recording_url) {
    	global $db_name, $db_host,$db_user,$db_passwd;
    
    	mysql_connect($db_host, $db_user, $db_passwd)
    		or die('Could not connect: ' . mysql_error());
    
    	mysql_select_db($db_name) or die('Could not select database');
    
    
    	//make sure inputs are db safe
    	$voicemail_exten = mysql_real_escape_string($voicemail_exten);
    	$caller_id = mysql_real_escape_string($caller_id);
    	$recording_url = mysql_real_escape_string($recording_url);
    
    	// Performing SQL query
    	$query = sprintf("insert into messages (message_frn_vmb_extension,"
    		. "message_date,message_from,message_audio_url,message_flag)"
    		. " values ('%s',now(),'%s','%s',0)", $voicemail_exten, $caller_id,
    		$recording_url);
    
    	mysql_query($query) or die('Query failed: ' . mysql_error());
    
    	$id = mysql_insert_id();
    	mysql_close();
    	return $id;
    }
    
    function updateMessageFlag($msg_id, $flag=0){
    	global $db_name, $db_host,$db_user,$db_passwd;
    
    	mysql_connect($db_host, $db_user, $db_passwd)
    		or die('Could not connect: ' . mysql_error());
    
    	mysql_select_db($db_name) or die('Could not select database');
    
    	//make sure inputs are db safe
    	$msg_id = mysql_real_escape_string($msg_id);
    	$flag = mysql_real_escape_string($flag);
    
    	// Performing SQL query
    	$query = sprintf("update messages set message_flag=%d where message_id=%d",
    		$flag, $msg_id);
    
    	mysql_query($query) or die('Query failed: ' . mysql_error());
    	mysql_close();
    }
    
    function getMessages($voicemail_exten,$flag=0){
    	global $db_name, $db_host,$db_user,$db_passwd;
    
    	mysql_connect($db_host, $db_user, $db_passwd)
    		or die('Could not connect: ' . mysql_error());
    
    	mysql_select_db($db_name) or die('Could not select database');
    
    	//make sure inputs are db safe
    	$voicemail_exten = mysql_real_escape_string($voicemail_exten);
    	$flag = mysql_real_escape_string($flag);
    
    	// Performing SQL query
    	$query = sprintf("select * from messages where message_flag=%d and "
    		. "message_frn_vmb_extension='%s' order by message_date", $flag,
    		$voicemail_exten);
    
    	$result = mysql_query($query) or die('Query failed: ' . mysql_error());
    
    	$messages = array();
    	while($line = mysql_fetch_array($result, MYSQL_ASSOC)) {
    		$messages[]=$line['message_id'];
    	}
    
    	mysql_close();
    
    	return $messages;
    }
    
    function getMessage($msg_id){
    	global $db_name, $db_host,$db_user,$db_passwd;
    
    	mysql_connect($db_host, $db_user, $db_passwd)
    		or die('Could not connect: ' . mysql_error());
    
    	mysql_select_db($db_name) or die('Could not select database');
    
    	//make sure inputs are db safe
    	$msg_id = mysql_real_escape_string($msg_id);
    
    	// Performing SQL query
    	$query = sprintf("select * from messages where message_id=%d",$msg_id);
    
    
    	$result = mysql_query($query) or die('Query failed: ' . mysql_error());
    
    	$message = array();
    	if($line = mysql_fetch_array($result, MYSQL_ASSOC)) {
    		$message['id']=$line['message_id'];
    		$message['date']=$line['message_date'];
    		$message['from']=$line['message_from'];
    		$message['url']=$line['message_audio_url'];
    	}
    
    	mysql_close();
    
    	return $message;
    }
    
    ?>
        

Leave a Message in a Mailbox

Next we will define the voice application for leaving a voicemail. It has three parts, one to gather and verify the target mailbox, one to prompt the user to record their message, and one part to save the message in the database. If you wished to integrate leaving a voicemail into another application, you'd redirect the call to leave_a_message.php?exten=[the mailbox extension] rather than asking the caller to provide the mailbox.

  • howtos/voicemail/pick_mailbox.php
    <?php
    
    require "Services/Twilio.php";
    include "messages.php";
    
    $error = false;
    
    //if we received an extension, attempt to look it up from the voicemailbox table
    if (strlen($_REQUEST['Digits'])) {
    	$exten = $_REQUEST['Digits'];
    
    	//if the mailbox exists, redirect the call to the leave_a_message.php
    	if (getMailbox($exten)) {
    		header("location: leave_a_message.php?exten=$exten");
    		exit();
    	} else {
    		$error=true;
    	}
    }
    
    $response = new Services_Twilio_Twiml();
    $gather = $response->gather();
    
    if($error)
    	$gather->say("Mailbox for extension $exten was not found");
    
    $gather->say("Enter the extension you wish to leave a message for, followed by"
    	. " the #sign");
    $response->say('I did not receive an extension.');
    $response->redirect('pick_mailbox.php');
    
    print $response;
    
    ?>
        

Prompt the caller to leave a message and record them.

  • howtos/voicemail/leave_a_message.php
    <?php
    
    require "Services/Twilio.php";
    include "messages.php";
    
    if (strlen($_REQUEST['exten'])) {
    	$exten = $_REQUEST['exten'];
    	$mailbox = getMailbox($exten);
    
    	//output TwiML to record the message
    	$response = new Services_Twilio_Twiml();
    	$response->say('Leave a message for ' . $mailbox['desc'] . ' at the beep');
    	$response->record(
    		array(
    			'action' => "handle_message.php?exten=$exten",
    			'maxLength' => '120')
    	);
    
    	// record will post to this url if it receives a message
    	// otherwise it falls through to the next verb
    	$response->gather()
    		->say("A message was not received, press any key to try again");
    
    	print $response;
    }
    
    ?>
        

Save the message they left into the database

  • howtos/voicemail/handle_message.php
    <?php
    
    require "Services/Twilio.php";
    include "messages.php";
    
    $exten = $_REQUEST['exten'];
    $url = $_REQUEST['RecordingUrl'];
    $caller_id = $_REQUEST['Caller'];
    
    if (strlen($exten) && strlen($url)) {
    	//save recording url and callerid as a message for that mailbox extension
    	addMessage($exten, $caller_id, $url);
    
    	$response = new Services_Twilio_Twiml();
    	$response->say('Thank you, good bye');
    	print $response;
    }
    
    ?>    

That's it! If you point your Twilio incoming phone number at pick_mailbox.php and call it, you will be prompted to pick a mailbox, prompted to leave a message, and the message will be saved and flagged as new.

Retrieve a Message in a Mailbox

Next lets define the php application that handles the phone call. Here we use the Twilio Response Library to facilitate TwiML creation. We no longer have to mix PHP and XML, keep the code clean and understandable. If you haven't already, visit the Response Library introduction to familiarize yourself.

  • howtos/voicemail/get_mailbox.php
    <?php
    
    require "Services/Twilio.php";
    require "messages.php";
    
    $error=false;
    
    if (strlen($_REQUEST['Digits'])) {
    	$exten = $_REQUEST['Digits'];
    	$mailbox = getMailbox($exten);
    	if ($mailbox===false) {
    		$error=true;
    	}else {
    		header("location: get_passcode.php?exten=$exten");
    		exit();
    	}
    }
    
    $response = new Services_Twilio_Twiml();
    
    if ($error) {
    	$response->say('Mailbox not found');
    } else {
    	$response->gather()
    		->say('Enter your mailbox extension and press the pound key');
    }
    
    print $response;
    
    ?>    

Once we have the mailbox, ask for the passcode. Look up the passcode for the mailbox and compare it to what the caller entered. If they match, move on the message_menu.php

  • howtos/voicemail/get_passcode.php
    <?php
    
    require "Services/Twilio.php";
    include "messages.php";
    
    if(strlen($_REQUEST['exten'])){
    	$exten=$_REQUEST['exten'];
    } else {
    	$response = new Services_Twilio_Twiml();
    	$response->say("An error occured in the voicemail system");
    	die((string) $response);
    }
    
    if(strlen($_REQUEST['Digits'])){
    	$code = $_REQUEST['Digits'];
    	$mailbox = getMailbox($exten);
    	if($mailbox['passcode']!=$code) {
    		$error=true;
    	} else {
    		header("location: message_menu.php?exten=$exten");
    		exit();
    	}
    }
    
    $response = new Services_Twilio_Twiml();
    
    if ($error) {
    	$response->say('Passcode incorrect, please try again');
    } else {
    	$response->gather()
    		->say("Enter the passcode for mailbox $exten");
    }
    
    print $response;
    
    ?>
        

Listen.php is passed an array of message ids and a starting point. As a caller listens to messages, he can interrupt the message with instructions like skip, save, or delete. If they hit 2 or 3, the code updates the message record, marking it saved or deleted. Each time the Gather is submitted or the Redirect is hit, the current message is incremented so you hear the next message.

  • howtos/voicemail/listen.php
    <?php
    
    
    require "Services/Twilio.php";
    include "messages.php";
    
    $first = true;
    
    if (strlen($_REQUEST['exten'])) {
    	$exten=$_REQUEST['exten'];
    } else {
    	$response = new Services_Twilio_Twiml();
    	$response->say("An error occured in the voicemail system");
    	die((string) $response);
    }
    
    $messages = array();
    $total_messages = 0;
    
    if (strlen($_REQUEST['messages'])) {
    	$msg_list = $_REQUEST['messages'];
    	$messages = preg_split("/,/",$_REQUEST['messages']);
    	$total_messages = count($messages);
    }
    
    $current_msg = 0;
    if (strlen($_REQUEST['current_msg'])) {
    	$current_msg = $_REQUEST['current_msg'];
    	$first = false;
    }
    
    if (strlen($_REQUEST['Digits'])) {
    	$digits = $_REQUEST['Digits'];
    
    	if($digits == 1){
    		//skip
    	} else if ($digits == 2){
    		//save
    		updateMessageFlag($messages[$current_msg],1);
    	} else if ($digits == 3){
    		//delete
    		updateMessageFlag($messages[$current_msg],2);
    	}
    
    	if($current_msg + 1 < $total_messages) {
    		$current_msg++;
    	} else {
    		$response = new Services_Twilio_Twiml();
    		$response->say("There are no more messages");
    		$response->say("Main menu");
    		$response->redirect("get_mailbox.php");
    		print $response;
    		exit();
    	}
    }
    
    $flag = 0;
    
    if (strlen($_REQUEST['flag'])) {
    	$flag = $_REQUEST['flag'];
    }
    
    $msg = getMessage($messages[$current_msg]);
    
    $url = $msg['url'];
    $from = $msg['from'];
    $date = $msg['date'];
    $msg_list = urlencode($msg_list);
    
    $response = new Services_Twilio_Twiml();
    $gather = $response->gather(
    	array(
    		"action" => "listen.php?exten=$exten&messages=$msg_list"
    			. "&current_msg=$current_msg",
    		"numDigits" => "1",
    		"timeout" => "5",
    	)
    );
    
    if($first) {
    	$gather->say("Press 1 to skip, 2 to save, 3 to delete");
    } else {
    	$gather->say("Next Message");
    }
    
    $message = "Message from $from received on "
    	. date('l jS \of F Y h:i A', strtotime($date));
    
    $gather->say($message);
    $gather->play($url);
    $gather->redirect("listen.php?exten=$exten&messages=$msg_list"
    	. "&current_msg=$current_msg&Digits=1");
    
    print $response;
    
    ?>
    
        

There are many, many ways to extend and improve on this example. You could add a hook to the handle_message.php file, having it email the audio URL when a message is saved. You could build a web interface using the data access functions in messages.php which would let you check and manage your voicemail in a web browser rather than over the phone. You can add custom greetings and audio prompts using your own recordings and the Play verb. Experiment!