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.
Codesandbox
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
Project setup
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.
1yarn create nuxt-app nuxtjs-phone-answering-machine2# OR3npx create-nuxt-app nuxtjs-phone-answering-machine4# OR5npm init nuxt-app nuxtjs-phone-answering-machine
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.
1cd nuxtjs-phone-answering-machine23yarn dev4# OR5npm run dev
Cloudinary registration
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
1touch .env
We can now add our credentials. If you do not have yours you can refer to the Account Details section of the dashboard.
1NUXT_ENV_BASE_URL=https://localhost:30002NUXT_ENV_CLOUDINARY_CLOUD_NAME=<your-cloudinary-cloud-name/>3CLOUDINARY_API_KEY=<your-cloudinary-api-key/>4CLOUDINARY_API_SECRET=<your-cloudinary-api-secret/>
Phone number registration
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.
Receiving and recording phone calls
Middleware setup
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.
1touch server-middleware\api.js
Let us register this middleware in the nuxt.config.js
file.
1// nuxt.config.js2export default {3 ...4 serverMiddleware: [5 { path: "/api", handler: "~/server-middleware/api.js" },6 ],7}
Requests to the /api
route will not be sent to our middleware.
Dependency installation
Let us now install all the dependencies we need to create the callback.
1yarn add body-parser cloudinary express Twilio
Callback implementation
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.
1// server-middleware/api.js23require('dotenv').config();45const app = require('express')();67const bodyParser = require('body-parser');89app.use(bodyParser.json());1011app.use(bodyParser.urlencoded({ extended: true }));1213const VoiceResponse = require('twilio').twiml.VoiceResponse;1415const cloudinary = require('cloudinary');1617cloudinary.config({18 cloud_name: process.env.NUXT_ENV_CLOUDINARY_CLOUD_NAME,19 api_key: process.env.CLOUDINARY_API_KEY,20 api_secret: process.env.CLOUDINARY_API_SECRET,21 secure: true22});2324const tag = "nuxtjs-phone-answering-machine";2526app.all('/callback', async (request, response) => {2728 const body = request.body;2930 if (body.CallStatus !== "completed") {31 const twiml = new VoiceResponse();32 twiml.say('Hello. Please leave a message after the beep.');33 twiml.record();34 twiml.hangup();35 response.type('text/xml');36 return response.send(twiml.toString());37 }3839 const maskedPhoneNumber = body.From.slice(0, 3) + body.From.slice(3, -2).replace(/[0-9]/g, "*") + body.From.slice(-2);4041 const uploaded = await cloudinary.v2.uploader.upload(42 body.RecordingUrl,43 {44 resource_type: "video",45 folder: "nuxtjs-phone-answering-machine",46 tags: [tag],47 context: `From=${maskedPhoneNumber}|FromCountry=${body.FromCountry}|FromCity=${body.FromCity}`48 },49 function (error, result) { console.log(result, error) });5051 return response.json({ uploaded });5253});5455module.exports = app
To ensure that we safeguard our user's privacy, we mask most digits of the phone numbers.
Configuring the callback
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
1ngrok 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.
Listing recordings
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.
1// server-middleware/api.js23...45app.all('/list', async (request, response) => {67 return await cloudinary.v2.api.resources_by_tag(8 tag,9 { resource_type: 'video', context: true },10 function (error, result) {11 return response.json(result);12 }13 );1415});1617module.exports = app
Showing recordings
Let us fetch the recordings in the asyncData
hook of the script section. This will load all the recordings into our page state.
1// pages/index.vue23<script>4export default {5 async asyncData () {6 const url = `${process.env.NUXT_ENV_BASE_URL}/api/list`;78 const recordings = await fetch(url)9 .then(response => response.json())10 .then(data => data.resources);1112 return {recordings};13 }14}15</script>
Now we can iterate the recordings
variable and display the recordings in our template section.
1<!-- pages/index.vue -->2<template>34 <div>5 <ul role="list">6 <li v-for="recording in recordings" :key="recording.public_id">7 <div>8 <div>9 <div>10 <div>11 <p>12 Recording from {{recording.context.custom.From}}13 ({{recording.context.custom.FromCountry}} {{recording.context.custom.FromCity}})14 </p>15 <p>16 <span>17 {{recording.created_at}}18 </span>19 </p>20 </div>21 <div>22 <div>23 <audio controls :src="recording.secure_url" >24 Your browser does not support the <code>audio</code> element.25 </audio>26 </div>27 </div>28 </div>29 </div>30 </div>31 </li>32 </ul>33 </div>34</template>
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