During non-working hours, it makes sense to route phone calls to an answering machine as opposed to leaving the phone unattended ringing at all times. Let us see how we can create a phone answering machine that records the phone calls and allows easy access.
The completed project is available on Codesandbox.
You can find the full codebase on my Github
To be able to follow along, knowledge of HTML, CSS, and JavaScript is required. Knowledge of Vue.JS is a plus but not required
We will be using Nuxt. JS to build this project. Nuxt.Js is a performance and convenient Vue.Js framework.
To get started, ensure you have yarn or npm v5.2+/v6.1+ installed. Open your terminal in your preferred working directory and run the following command.
yarn create nuxt-app nuxtjs-phone-answering-machine
# OR
npx create-nuxt-app nuxtjs-phone-answering-machine
# OR
npm init nuxt-app nuxtjs-phone-answering-machine
Code language: PHP (php)
Once you run the above command, you will receive a set of setup questions. Here are our recommended defaults:
Project name: nuxtjs-phone-answering-machine Programming language: JavaScript Package manager: Yarn UI framework: Tailwind CSS Nuxt.js modules: N/A Linting tools: N/A Testing framework: None Rendering mode: Universal (SSR/SSG) Deployment target: Server (Node.js hosting) Development tools: None
Once the setup process is complete, you may now enter the directory and run the project. It will be accessible on http://localhost:3000.
cd nuxtjs-phone-answering-machine
yarn dev
# OR
npm run dev
Code language: PHP (php)
We need to store our recordings somewhere easily accessible. For this, we will use Cloudinary, a media management platform with a set of rich APIs and SDKs. If you do not have an account create one here. Let us link our project to our account by setting up our credentials in our .env
file. This is the file that contains our environmental variables. These are values we do not want to save in our code and code repository. Let’s create our .env
file
touch .env
Code language: CSS (css)
We can now add our credentials. If you do not have yours you can refer to the Account Details section of the dashboard.
NUXT_ENV_BASE_URL=https://localhost:3000
NUXT_ENV_CLOUDINARY_CLOUD_NAME=<your-cloudinary-cloud-name/>
CLOUDINARY_API_KEY=<your-cloudinary-api-key/>
CLOUDINARY_API_SECRET=<your-cloudinary-api-secret/>
Code language: JavaScript (javascript)
We will be using Twilio to get a phone number. If you don’t have an account, feel free to set up one here. You should get a trial account which allows you to get a trial phone number. Once you register a trial phone number, upgrade the account to use Twilio voice.
Let us prepare our Twilio voice callback. First, we need to enable server middleware in our Nuxt.Js project. Let’s create api.js
in the server-middleware
folder.
touch server-middleware\api.js
Code language: CSS (css)
Let us register this middleware in the nuxt.config.js
file.
// nuxt.config.js
export default {
...
serverMiddleware: [
{ path: "/api", handler: "~/server-middleware/api.js" },
],
}
Code language: JavaScript (javascript)
Requests to the /api
route will not be sent to our middleware.
Let us now install all the dependencies we need to create the callback.
yarn add body-parser cloudinary express Twilio
Now let us prepare a \api\callback
endpoint to receive our Twilio phone number voice callback. If the call is not complete, we will play a message and start recording. Once the call is complete, we will receive the recording, upload it to Cloudinary and add some context metadata.
// server-middleware/api.js
require('dotenv').config();
const app = require('express')();
const bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
const VoiceResponse = require('twilio').twiml.VoiceResponse;
const cloudinary = require('cloudinary');
cloudinary.config({
cloud_name: process.env.NUXT_ENV_CLOUDINARY_CLOUD_NAME,
api_key: process.env.CLOUDINARY_API_KEY,
api_secret: process.env.CLOUDINARY_API_SECRET,
secure: true
});
const tag = "nuxtjs-phone-answering-machine";
app.all('/callback', async (request, response) => {
const body = request.body;
if (body.CallStatus !== "completed") {
const twiml = new VoiceResponse();
twiml.say('Hello. Please leave a message after the beep.');
twiml.record();
twiml.hangup();
response.type('text/xml');
return response.send(twiml.toString());
}
const maskedPhoneNumber = body.From.slice(0, 3) + body.From.slice(3, -2).replace(/[0-9]/g, "*") + body.From.slice(-2);
const uploaded = await cloudinary.v2.uploader.upload(
body.RecordingUrl,
{
resource_type: "video",
folder: "nuxtjs-phone-answering-machine",
tags: [tag],
context: `From=${maskedPhoneNumber}|FromCountry=${body.FromCountry}|FromCity=${body.FromCity}`
},
function (error, result) { console.log(result, error) });
return response.json({ uploaded });
});
module.exports = app
Code language: JavaScript (javascript)
To ensure that we safeguard our user’s privacy, we mask most digits of the phone numbers.
Let us first enable our endpoint and our app to be accessible online. We are going to use ngrok for this. Follow the setup instructions here to download and authenticate your ngrok instance. Finally, instead of starting a tunnel to port 80 as instructed in the setup instructions, we tunnel to port 3000
ngrok http 3000
Proceed to the Manage > Active phone numbers section to configure our callback. Open your test phone number and input the ngrok URL you received after tunneling. Add it to the A call comes in
input: https://your-ngrok-url.ngrok.io/api/callback
Now when you place a phone call, it will be received, recorded, and uploaded to your Cloudinary account.
We need to enable our Nuxt.Js app to receive a list of all recordings. Let us create an api/list
endpoint for this purpose. We will use the Cloudinary Admin API > Get resources by tag for this.
// server-middleware/api.js
...
app.all('/list', async (request, response) => {
return await cloudinary.v2.api.resources_by_tag(
tag,
{ resource_type: 'video', context: true },
function (error, result) {
return response.json(result);
}
);
});
module.exports = app
Code language: JavaScript (javascript)
Let us fetch the recordings in the asyncData
hook of the script section. This will load all the recordings into our page state.
// pages/index.vue
<script>
export default {
async asyncData () {
const url = `${process.env.NUXT_ENV_BASE_URL}/api/list`;
const recordings = await fetch(url)
.then(response => response.json())
.then(data => data.resources);
return {recordings};
}
}
</script>
Code language: HTML, XML (xml)
Now we can iterate the recordings
variable and display the recordings in our template section.
<!-- pages/index.vue -->
<template>
<div>
<ul role="list">
<li v-for="recording in recordings" :key="recording.public_id">
<div>
<div>
<div>
<div>
<p>
Recording from {{recording.context.custom.From}}
({{recording.context.custom.FromCountry}} {{recording.context.custom.FromCity}})
</p>
<p>
<span>
{{recording.created_at}}
</span>
</p>
</div>
<div>
<div>
<audio controls :src="recording.secure_url" >
Your browser does not support the <code>audio</code> element.
</audio>
</div>
</div>
</div>
</div>
</div>
</li>
</ul>
</div>
</template>
Code language: HTML, XML (xml)
With the above code, our project can now receive, record incoming calls, and display recordings for access. To learn more about Twilio Voice API feel free to read the documentation. Feel free to also read the Cloudinary documentation to see what is possible with this powerful platform