Tropo and Node.js Automated Voice Mail
May 31, 2018
Introduction
I use Tropo to answer my phone. Tropo is cloud software which creates a phone number and provides the web API to answer calls and record voice messages. I used the Node.js API library because of its simplicity and ease of setup.
Links
The Tropo Node API client can be downloaded [here] (https://github.com/tropo/tropo-webapi-node, “tropo”). I will let you install Node.js yourself and just show the important components of the script.
Install Tropo
If you have Node.js installed, and are using the Node Package Manager (npm), just do the following command as root:
# npm install tropo-webapi -g
This will install Tropo globally on your machine.
Node.js code
Now create a Node.js server to record voicemail messages. You will need to have a server.js file with the express Node module and complete setup. The call to tropo.record will post the multipart wav file to https://test.me/tel/voice/rec. I used Multer to get the file from the HTTP Post endpoint. I had difficulties with this code until I found out that the name supplied to Post was called ‘filename’. Without this string, the Posted multi-part will fail.
//tropo functions
var tropowebapi = require('tropo-webapi');
var sys = require('sys');
//Install multi part to access files >npm install --save multer
var multer = require('multer');
var upload = multer({ dest: 'uploads/' });
var fs = require('fs');
//telephone functions
app.post('/tel/voice', function(req, res){
var tropo = new tropowebapi.TropoWebAPI();
if(req.body['session']['from']['channel'] == "TEXT") {
tropo.say("This application is voice only. Please call in using a regular phone or SIP phone.");
tropo.on("continue", null, null, true);
res.send(TropoJSON(tropo));
}
else{
tropo.say("Hello!");
var say = new Say("Please ree cord your message after the beep and do not hang up until further instructions.");
var choices = new Choices(null, null, "#");
tropo.record(3, false, true, true, choices, 'audio/wav', 3, 60, null, null, "recording", null, say, 5, null, "https://test.me/tel/voice/rec", null, null);
tropo.on("continue", null, "/tel/voice/answer", true);
tropo.on("incomplete", null, "/tel/voice/timeout", true);
tropo.on("error", null, "/tel/voice/error", true);
}
res.send(tropowebapi.TropoJSON(tropo));
});
app.post('/tel/voice/answer', function(req, res){
var tropo = new tropowebapi.TropoWebAPI();
tropo.say("Perfecto! Your recording saved and we will contact you shortly! Thank you! Have a great day! Goodbye!");
res.send(tropowebapi.TropoJSON(tropo));
});
app.post('/tel/voice/timeout', function(req, res){
var tropo = new tropowebapi.TropoWebAPI();
tropo.say("Sorry, I didn't hear anything. Please call back and try again.");
res.send(tropowebapi.TropoJSON(tropo));
});
app.post('/tel/voice/error', function(req, res){
// Create a new instance of the TropoWebAPI object.
var tropo = new tropowebapi.TropoWebAPI();
tropo.say("Recording failed. Please call back and try again.");
res.send(tropowebapi.TropoJSON(tropo));
});
//must be named filename to upload
app.post('/tel/voice/rec', upload.single('filename'), function (req, res, next) {
console.log(req.file.filename);
var tmp_path = req.file.path;
var target_path = 'uploads/' + req.file.filename + '.wav';
var src = fs.createReadStream(tmp_path);
var dest = fs.createWriteStream(target_path);
src.pipe(dest);
var tropo = new tropowebapi.TropoWebAPI();
res.send(tropowebapi.TropoJSON(tropo));
});
Cron Job
I set up a cron job to run every minute to email me the voice messages. It is possible to email directly from Node, however, I found it not as straight forward.
Crontab -e run every minute.
* * * * * cd /home/dev/ && /usr/bin/python3 /home/dev/cronemail.py > /tmp/cron.log 2>&1
Here is the cronemail.py Python code.
#!/usr/bin/env python3
import os
import sys
import smtplib
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.mime.audio import MIMEAudio
from email.mime.multipart import MIMEMultipart
from email.header import Header
from email.utils import formataddr
from os import listdir
from os.path import isfile, join
import mimetypes
# send voice recordings to email and delete old recordings.
class Recording():
def __init__(self):
self.emailSent = False
def sendMultipartEmail(self, sender, recipient, subject, attachFiles, body, bodyFormat):
msg = MIMEMultipart()
msg['Subject'] = subject
#msg['From'] = formataddr((str(Header('MyWebsite', 'utf-8')), 'from@mywebsite.com'))
msg['From'] = sender
msg['To'] = recipient
#msg.preamble = body
part = None
if(bodyFormat == 'html'):
part = MIMEText(body, 'html')
else:
part = MIMEText(body, 'plain')
for f in attachFiles:
ctype, encoding = mimetypes.guess_type(f)
if ctype is None or encoding is not None:
# No guess could be made, or the file is encoded (compressed), so
# use a generic bag-of-bits type.
ctype = 'application/octet-stream'
maintype, subtype = ctype.split('/', 1)
with open(f, 'rb') as fp:
img = MIMEAudio(fp.read(), _subtype=subtype)
msg.add_header('Content-Disposition', 'attachment', filename=os.path.basename(fp.name))
fp.close()
msg.attach(img)
s = smtplib.SMTP('mail.server.com', 587)
#s.connect('mail.server.com', 587) #this hangs on Debian, so I removed it.
s.starttls()
s.login('username', 'password')
msg.attach(part)
s.send_message(msg)
s.quit()
#
def main():
app = Recording()
EmailSender = "test@mail.server.com"
EmailRecipient = "test@gmail.com"
EmailSubject = "Voice Recordings"
bodyFormat = "html" #or "plain"
body = """\
<html>
<head></head>
<body>
<p>Hi!<br>
Here is new voice recordings!<br>
%s<br>
</p>
<p>
More automated communication features at <a href="%s">test.me</a>.
</p>
</body>
</html>
"""
body = body % ("", 'https://test.me')
#/uploads/recording.wav
#gather recordings
recordings = [f for f in listdir('/home/dev/breve/uploads') if isfile(join('/home/dev/breve/uploads', f))]
i = 0
for name in recordings:
recordings[i] = "/home/dev/breve/uploads/" + name
i += 1
if(len(recordings) > 0):
app.sendMultipartEmail(EmailSender, EmailRecipient, EmailSubject, recordings, body, bodyFormat)
#delete the recordings
print(recordings)
for name1 in recordings:
os.remove(name1)
print("Sent and removed")
#
if __name__ == "__main__":
main()