Introduction
In this tutorial, you'll learn how to build your voicemail. We will use Express.js and Socket.io.
You can find the complete code on GitHub.
Outline and Prerequisites
Node.js installed
Ngrok is installed to expose your local development server to the internet.
A basic understanding of JavaScript and Node.js
A Vonage API account to access our Voice API
Vonage API Account
To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.
This tutorial also uses a virtual phone number. To purchase one, go to Numbers > Buy Numbers and search for one that meets your needs.
Project Structure
For your reference, this is how the project directory will look like:
Initialize the Project
Create a directory for your application and change into it.
Run
npm init -y
to initialize your project.
Set Up Ngrok
To make our local server's endpoints accessible from the internet without deploying our application to a production environment, we'll expose our local machine using ngrok.
Open a new terminal or command prompt window and initiate ngrok on port 3000 with the following command: ngrok http 3000
.
Create a Vonage Application and Add the Private Key
Log in/ Sign Up to your Vonage API dashboard and navigate to "Your applications" under Build & Manage.
Click "+ Create a new application". Give your application a name and select "Generate public and private key." Then, download the generated private key.
In the "Capabilities" section, enable "Voice" and provide the URLs for the event, and answer webhook endpoints. Use your Ngrok URL:
${NGROK_URL}/event
for Event URL and${NGROK_URL}/answer
for Answer URL.Save your application. Note the Application ID and API Key are provided.
Add the downloaded private key to your project directory.
Check out our ngrok guide for help with the webhook endpoints
Purchase a Virtual Number
Next, we'll secure a virtual number to serve as the point of contact for incoming calls. This number will be associated with our Vonage application. Here's the process for obtaining one:
Install the Vonage CLI: npm install @vonage/cli -g
Search for available numbers in your desired region: vonage numbers:search US
(replace US with your country code if needed). Buy a number: vonage numbers:buy 12079460000 US
.
Remember that some numbers cannot be purchased via the Command Line, so you must purchase them via the dashboard. You can do so by going to the Vonage Dashboard and the Buy Numbers page. Tick 'Voice' in the search filter and select the country where you want to buy a number.
Link a Virtual Phone Number to the Application
Once we've selected our number, we must link it to our Vonage application. This can be done directly through the Vonage Dashboard or using the CLI. By linking the number, we ensure calls are properly directed to our application.
vonage apps:link --number=12079460000 VONAGE_APPLICATION_ID
You can also do it from the dashboard. Go to the Application page and click on the application you created earlier. Click the 'Link' button next to the number you want to link in the Voice section.
Create Environment Variables
Create a .env
file in your project root with the following, filling in your data from the Vonage Dashboard: Check this blog post if you're not familiar with How to Use Environment Variables in Node.js.
Install Dependencies
We are going to use the following dependencies in our project: Express
Socket.io
@vonage/server-client
) body-parser
dotenv
You can install them by running the following command:
Server Setup
Create the index.js
file and initialize your server with the required packages. Configure a Vonage client using variables from .env
and outline the /answer
, /event
, and /voicemail
routes.
require("dotenv").config();
const express = require("express");
const app = express();
const http = require("http").Server(app);
const io = require("socket.io")(http);
const bodyParser = require("body-parser");
const { FileClient } = require("@vonage/server-client");
// Ensure these environment variables are correctly set in your .env file
const fileClient = new FileClient({
applicationId: process.env.VONAGE_APPLICATION_ID,
privateKey: process.env.PRIVATE_KEY,
});
app.use(express.static("public"));
app.use(bodyParser.json());
app.get('/answer', (req, res) => {});
app.post('/event', (req, res) => {});
app.post('/voicemail', (req, res) => {});
http.listen(3000, () => console.log("Server is running on port 3000"));
Define Call Handling Logic
In the /answer
route, we set up a call flow using an NCCO (Call Control Object) to interact with callers. This script greets the caller, prompts them to leave a message after a beep, and ends the recording when the '#' key is pressed, followed by a farewell message. The res.send(ncco);
line sends this instruction set back to Vonage, executing it during the call.
app.get("/answer", (req, res) => {
const ncco = [
{
action: "talk",
voiceName: "Ivy",
text: "Please record your message for me after the beep. Press # to end.",
},
{
action: "record",
eventUrl: [`${process.env.URL}/voicemail`],
endOnKey: "#",
beepStart: true,
},
{
action: "talk",
voiceName: "Ivy",
text: "Message recorded. Bye!"
}
];
res.send(ncco);
});
Customize the greeting to make the call more personal. Many voice options allow you to adjust the vocal tone, pitch, and style to match your preferences or brand identity. Explore different voice styles to find the perfect match for your application. Note that some voice styles may not support SSML (Speech Synthesis Markup Language), so it's worth checking the list of supported languages and styles.
Handle Events and Voicemails
The /event
endpoint processes various events sent by the Vonage API. When an event occurs, such as an answered call or a recorded voicemail, the Vonage API sends data related to that event to this endpoint. Our server logs this event data to the console for visibility and returns a 204 (No Content response) to acknowledge receipt. This minimal handling is sufficient for many applications, though you could extend this to perform specific actions based on event types.
Implement logic in /event
to process events.
app.post("/event", (req, res) => {
console.log("Event received:", req.body);
res.sendStatus(204);
});
When someone leaves a voicemail message, the Vonage API sends a POST request to our /voicemail
endpoint with details about the recording, including a URL where the recorded audio file can be accessed.
Our server simulates saving this recording by creating a filename based on the current timestamp and pretending to save the file at a specified path. This simplification is for demonstration purposes; you would likely download the recording from the provided URL and store it more securely in an actual application.
After "saving" the file, the server emits a voicemail event to all connected clients through Socket.io, passing along the filename and the current date and time.
In /voicemail
, simulate saving voicemails and emit a voicemail
event to the client.
app.post("/voicemail", (req, res) => {
const filename = `voicemail-${Date.now()}.mp3`;
const path = `${__dirname}/public/voicemails/${filename}`;
fileClient
.downloadFile(req.body.recording_url, path)
.then(() => {
io.emit("voicemail", {
date: new Date().toISOString(),
file: filename,
});
res.sendStatus(200);
})
.catch((err) => {
console.error("Error saving file:", err);
res.status(500).send(err);
});
});
http.listen(3000, () => console.log("Server is running on port 3000"));
The User Interface
In public/index.html
, we will add an <audio>
element for playing voicemail messages and a container for listing voicemails, allowing the voicemail receipt to play or delete messages received.
<meta charset="utf-8">
<title>Voicemail</title>
<audio>Sorry, your browser does not support this audio.</audio>
<div id="voicemails" class="voicemails">
<h2>New Voicemails</h2>
<!-- Voicemails will be dynamically added here -->
</div>
Display the Received Voice Mail Messages
Our JavaScript enriches the user interface by connecting with the server via Socket.io for real-time updates, like when new voicemails arrive. It automatically adds these voicemails to a list, showing their date and providing buttons to play or delete them. While the play button uses an <audio>
element for playback, the delete button only removes the voicemail from the display, not the server. Enhancing the application to delete voicemails from the server would also be a valuable improvement, requiring extra server-side logic to handle file deletions securely.
Add the following code to your index.html file:
<script src="/socket.io/socket.io.js"></script>
<script>
const voicemailsDiv = document.getElementById("voicemails");
const audio = document.querySelector("audio");
document.addEventListener("click", function (e) {
let el = e.target;
if (el.classList.contains("play_btn")) {
audio.src = el.getAttribute("data-src");
audio.play();
console.log("Playing audio from:", audio.src);
}
if (el.classList.contains("del_btn")) {
el.closest(".voicemail-item").remove();
console.log("Voicemail deleted");
}
});
const socket = io();
socket.on("connect", () => {
console.log("Connected to server via Socket.io");
});
socket.on("voicemail", (vm) => {
console.log("Received voicemail event:", vm);
const voicemailElement = document.createElement("div");
voicemailElement.classList.add("voicemail-item");
voicemailElement.innerHTML = `
<div>Date: ${vm.date}</div>
<button class="play_btn" data-src="/${vm.file}">Play</button>
<button class="del_btn">Delete</button>
`;
voicemailsDiv.appendChild(voicemailElement);
});
</script>
Test It Out
To get your server running, run node index.js
in your terminal from the root of your directory, and make sure ngrok is running.
If you call the phone number associated with your Vonage application, you'll hear the greeting you provided with the NCCOs. Once you record your message, it should appear on your HTML page on localhost:3000.
Conclusion
You've now created a voicemail system, enabling callers to leave messages displayed on a webpage. While current functionality does not extend to server-side file deletion, addressing this would be a valuable enhancement.
Chat with us on our Vonage Developer Community Slack or send us a message on X.