Building Voicemail with Twilio and Java

January 20, 2021
Written by
Reviewed by

Building Voicemail with Twilio and Java

One useful and common way to use Twilio is to create a phone number which forwards to your real phone. You can hand out the Twilio number to colleagues or customers and take business calls on your personal phone without worrying about handing out your real phone number. This also gives you a lot of options for how to handle these calls - you have the full power of Twilio at your disposal.

In this post I'll show you how to create a voicemail system that will jump in when your cell number is busy, or when you don't answer the call. It will play a message to your caller, then record their message and text you a link to the recording when it's done.

Primer: How Twilio Handles Calls

When someone dials your Twilio number, an HTTP request is sent to a URL that you provide. The web server handling that URL should respond with TwiML which will instruct Twilio what to do next. To program the behaviour of a Twilio number you will need to create a web server which can take requests from Twilio and respond with TwiML, a language based on XML that helps you define how Twilio should handle the call.

Schematic of a phone call being answered by Twilio, and Twilio making an HTTP request to "your app".  The TwiML returned by "your app" tells Twilio to say "Ahoy!" and play a sound (of Rick Astley)

In this post I'll use Java with Spring as the web framework, but you could use the same approach with any programming language.

Project Setup

You will need:

Both of these can be installed using SDKMAN! which I highly recommend. Once you have installed SDKMAN! install Java and the Spring Boot CLI like this:

# This installs Java 15 - if you want an older version
# run `sdk list java` to see what's available
sdk install java 15.0.1.hs-adpt
sdk install springboot

These commands will add some config to your shell environment, so it's a good idea to close the terminal and open a new one after running them.

You will also need a Java IDE. I like IntelliJ but Eclipse and VS Code are popular too.

Finally, you'll need a Twilio account.

Creating your new codebase

In an empty directory, initialize your project using the Spring CLI with:

spring init \
  --dependencies web \
  --groupId com.example \
  --artifactId twilio-voicemail \
  --extract

This creates a fresh Spring Boot project, configured with the web dependency. Open it in your IDE and let's get cracking.

Find the class called DemoApplication in the com.example.twiliovoicemail package. You don't need to change anything in that class, but alongside it in the same package create a class called VoicemailHandler which will hold all your code.

In the project root there is a file called pom.xml which defines the build and all the dependencies for the project. You will need the Twilio Java Helper Library so add the following in the <dependencies> section:

<dependency>
    <groupId>com.twilio.sdk</groupId>
    <artifactId>twilio</artifactId>
    <version>8.6.0</version>
</dependency>

[see this code on GitHub]

As of right now, the latest version is 8.6.0 - you can (and should) always check for a newer version at mvnrepository.com.

Building up the app

One thing I love about programming is the way it forces you to break a problem apart into tiny pieces. So what are the pieces of this app? You need to instruct Twilio to:

  1. Answer incoming calls and redirect to your real cell phone number,
  2. If you answer it, nothing more needs to be done. However if the line is busy, or the call rings out, play a message and start recording,
  3. Once the recording is finished, send a notification that there is a new recording to listen to.

Each of these will be handled by a different endpoint on the web server, so you can build them in order. To mark the VoicemailHandler class as containing HTTP endpoints, use the @RestController annotation. The class should look like this:

package com.example.twiliovoicemail;

import org.springframework.web.bind.annotation.RestController;

@RestController("TwiML")
public class VoicemailHandler {
    
}

[see this code on GitHub]

It's necessary to give the annotation a value (I've used TwiML here) - we'll see why later.

Answering and redirecting incoming calls

Create a method called initialAnswer in your VoicemailHandler class which returns the TwiML for answering the call and redirecting to your cell phone:

private final String MY_CELLPHONE_NUMBER = System.getenv("MY_CELLPHONE_NUMBER");
private final int ANSWERPHONE_TIMEOUT_SECONDS = 10;

@GetMapping(value = "/initial-answer", produces = "application/xml")
public String initialAnswer(){
            return new VoiceResponse.Builder()
                    .dial(new Dial.Builder()
                        .number(MY_CELLPHONE_NUMBER)
                        .timeout(ANSWERPHONE_TIMEOUT_SECONDS)
                        .action("/handle-unanswered-call")
                        .method(HttpMethod.GET)
                        .build())
                .build().toXml();
}

[see this code on GitHub - necessary imports are at the top of the code]

There's a few things going on here, so let's step through it:

  • lines 1-2 define some constants which will be used later in the code so it's helpful to have them defined outside of any method. Note that your real cell phone number is defined as an environment variable. More on those later.
  • line 4 uses the @GetMapping annotation to tell Spring to use this method for HTTP GET requests to the /initial-answer endpoint. You'll use that later when configuring the number in the Twilio console. It also sets the correct Content-Type.
  • lines 5-14 use the Twilio Helper Library to build up TwiML, which uses the Dial TwiML verb to connect out to another number.
  • the Dial verb takes a number of attributes:
    • the number to transfer the incoming call to,
    • the timeout for deciding when the call is unanswered. This timeout is configured as 10 seconds (on line 2) but feel free to change it (noting that Twilio adds a buffer of up to 5 seconds to account for the time taken to establish the call),
    • The action and method tell Twilio where to look for the next set of TwiML instructions after the call is established, rejected or timed out.

The .toXml() method call at the end creates a String of TwiML. It is perfectly possible to write the XML by hand, but for this level of complexity I think it's worth using the library to make sure everything is correctly constructed.

This is a good point to check that everything is working correctly in the project setup. First thing to do is set the MY_CELLPHONE_NUMBER environment variable (How To Set Environment Variables) . Your IDE will have a way to set it. I use EnvFile for IntelliJ, or export MY_CELLPHONE_NUMBER=+44xxxxxxxx on the command line.

MY_CELLPHONE_NUMBER should be the number of your real phone, in E.164 format. You won't need to refer to your Twilio number in-code, so you could use this same server to add voicemail to as many Twilio numbers as you like.

Start the server. You can do this with ./mvnw spring-boot:run in a terminal or by running the main method in DemoApplication through your IDE.

Screenshot of IntelliJ IDEA showing where the button is to run the DemoApplication class

Once it's started up, hit http://localhost:8080/initial-answer in your browser. It should respond with:

<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Dial action="/handle-unanswered-call" 
        method="GET"
        timeout="10">
    +44xxxxxxxxxxxx
  </Dial>
</Response>

If you see that, you're good to go! If you see *almost* that but without your cell phone number then that means the MY_CELLPHONE_NUMBER environment variable hasn't been found so double-check that you are setting it correctly.

The voicemail message

Add another annotated method for the /handle-unanswered-call endpoint, below initialAnswer:

@GetMapping(value = "/handle-unanswered-call", produces = "application/xml")
public String handleUnansweredCall(@RequestParam("DialCallStatus") String dialCallStatus){

   if ("busy".equals(dialCallStatus) || "no-answer".equals(dialCallStatus)){
       return voicemailTwiml();
   }

   return null;
}

[see this code on GitHub - necessary imports are at the top of the code]

One parameter passed in this request from Twilio is DialCallStatus. The voicemail code only needs to trigger on busy and no-answer statuses. This method does the check, and calls through to voicemailTwiml if necessary. Add the voicemailTwiml method below handleUnansweredCall:

private String voicemailTwiml() {
   return new VoiceResponse.Builder()
       .pause(new Pause.Builder().length(2).build())
       .play(new Play.Builder("/message.mp3").build())
       .record(new Record.Builder()
           .playBeep(true)
           .action("/recordings")
           .build())
       .build().toXml();
}

[see this code on GitHub - necessary imports are at the top of the code]

This generates TwiML that pauses for a couple of seconds before playing a message and starting a recording. You could record your own message.mp3or use this one which is my best impression of Stephen Merchant as Wheatley in Portal 2. I recorded it using this website. However you get it, put message.mp3 file in src/main/resources/static. Spring will automatically serve static files from that directory as long as you have named all your @RestController beans, which is why we called ours TwiML above

You could also use a Say verb if you don't want to record your own message - replace the line 4 (starting with .play()) with this:

.say(new Say.Builder("Sorry I can't take your call at the moment, please leave a message").build())

The Record verb takes an action option that tells Twilio which endpoint to call once the recording is complete.

Handling completed recordings

The last endpoint is /recordings, called after someone has left you a message. Useful parameters passed by Twilio in here are:

  • RecordingUrl - you can download the recording from here in wav format, or append .mp3 to fetch it in that format.
  • From - the caller's number
  • To - the number they dialled, ie your Twilio number.

The Java code to handle this will take those parameters and send an SMS to your cell phone number with all the details:

@PostMapping("/recordings")
public void handleRecording(
   @RequestParam("RecordingUrl") String requestUrl,
   @RequestParam("From") String callerNumber,
   @RequestParam("To") String twilioNumber){

   String mp3RecordingUrl = requestUrl + ".mp3";

   String smsNotification = String.format("You got an answerphone message from %s - listen here: %s", callerNumber, mp3RecordingUrl);

   Message.creator(
       new PhoneNumber(MY_CELLPHONE_NUMBER),
       new PhoneNumber(twilioNumber),
       smsNotification)
       .create();
}

[see this code on GitHub - necessary imports are at the top of the code]

If you are using this code for voicemail on more than one Twilio number, you will want to include the twilioNumber in the message too.

There is one more thing to add to the code. To send the SMS, you need an authenticated Twilio client. This only needs to be done once for the whole app, so add a static block at the top of the class:

static {
   Twilio.init(
       System.getenv("TWILIO_ACCOUNT_SID"),
       System.getenv("TWILIO_AUTH_TOKEN"));
}

[see this code on GitHub - necessary imports are at the top of the code]

You need to set a couple more variables for this for TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN - you can find the values you need on your Twilio console. These should be set as environment variables like MY_CELLPHONE_NUMBER - make sure they never get committed into a public repo.

This is all the code you need - less than a hundred lines in a single class. Now move on to configuring it to handle real phone calls.

Using your server to handle real phone calls

You need to do three things to get the web server connected and working:

  • Make sure that Twilio can access the server,
  • buy a phone number from Twilio,
  • configure the phone number to call the /initial-answer endpoint when there is an incoming call.

For the first part, you need to make sure that your server can be reached over the internet: localhost isn't going to cut it here. There are a lot of choices for deploying Java apps publicly, but a simple way to get things going when testing is to use ngrok - a tool which creates a tunnel so that your localhost server has a public URL. I use ngrok http 8080 to spin this up, but however you do it, you will need the full URL of the /initial-answer endpoint in the next step.

If you've already got a Twilio number that you want to use then great. If not then head over to the Twilio console to log in and pick yourself a new phone number. If you don't already have a Twilio account you can sign up for one here. Once you have the number, on the number's configuration page set the public URL of your web server as the webhook for "When a call comes in" - don't forget to set the method to HTTP GET in the console matching the @GetMapping annotation on initialAnswer().

Screenshot of the Twilio console showing where to set the "When a call comes in" webhook

Make sure you don't forget to click "Save", and you're finished. Test it out by having someone call your Twilio number - you'll see a call come in from them on your cell phone, which you can answer, reject or ignore as you wish. If they leave a message you will get an SMS with a link to the recording which you should be able to listen to by clicking the link right from your messaging app.

Summing up

If you've followed along with this post, you now have a working voicemail for your Twilio number. You have also learned a bit about how Twilio uses webhooks to handle incoming calls, and how to build them out using Java and Spring Boot. This is just one possibility for handling incoming calls with Twilio, if voicemail isn't quite what you want, how about...

Whatever you're building with Twilio and Java, I'd love to hear about it. Get in touch by email, or Twitter, or leave me a voicemail: