E-mail to voice call – with Asterisk, Postfix and Cepstral

A few times recently I’ve wanted to be able to turn an e-mail into a voice call. This would be especially handy for emergency server monitoring and notification.

Here is my first attempt. It’s also my first attempt at writing something in Python so you definitely use at your own risk!

There is room for improvement as there is no validation on any of the fields extracted from the e-mail.

It also assumes that these components are already in place –

  • Asterisk (with Astersk Manager Interface)
  • E-mail server (I’m using Postfix)
  • Ceptral text-to-speech (www.cepstral.com) – installed in /opt/swift/bin
  • Python (I’m using v2.4.3)

First we need to pipe the incoming e-mail to our Python script. For this I added a line to /etc/aliases –

emailspeak: "|/usr/local/bin/emailspeak.py"

and ran newaliases –

newaliases

Now for the script which is called ‘/usr/local/bin/emailspeak.py’

You will need to change at least the USER, SECRET and TRUNK settings at the top of the script to match you Asterisk setup.

#!/usr/bin/env python
# emailspeak.py by sysadminman - http://sysadminman.net
# v1.0  13/2/10

# Import libs we need
import sys, time, email, email.Message, email.Errors, email.Utils, smtplib, os, socket, random
from datetime import date
from email.Iterators import typed_subpart_iterator
from time import sleep

# Asterisk Manager connection details
HOST = '127.0.0.1'
PORT = 5038
# Asterisk Manager username and password
USER = 'manageruser'
SECRET = 'managerpass'
# Set the name of the SIP trunk to use for outbound calls
TRUNK = 'trunkforcalls'

# Generate a random number as a string. We'll use this for file names later on
callnum = str(random.randint(1, 100000000))

# Taken from here, with thanks - http://ginstrom.com/scribbles/2007/11/19/parsing-multilingual-email-with-python/
def get_charset(message, default="ascii"):
    """Get the message charset"""

    if message.get_content_charset():
        return message.get_content_charset()

    if message.get_charset():
        return message.get_charset()

    return default

# Taken from here, with thanks - http://ginstrom.com/scribbles/2007/11/19/parsing-multilingual-email-with-python/
def get_body(message):
    """Get the body of the email message"""

    if message.is_multipart():
        #get the plain text version only
        text_parts = [part
                      for part in typed_subpart_iterator(message,
                                                         'text',
                                                         'plain')]
        body = []
        for part in text_parts:
            charset = get_charset(part, get_charset(message))
            body.append(unicode(part.get_payload(decode=True),
                                charset,
                                "replace"))

        return u"\n".join(body).strip()

    else: # if it is not multipart, the payload will be a string
          # representing the message body
        body = unicode(message.get_payload(decode=True),
                       get_charset(message),
                       "replace")
        return body.strip()

# Read the e-mail message that has been piped to us by Postfix
raw_msg = sys.stdin.read()
emailmsg = email.message_from_string(raw_msg)

# Extract database Fields from mail
msgfrom = emailmsg['From']
msgto =  emailmsg['To']
msgsubj = emailmsg['Subject']
msgbody = get_body(emailmsg)

# Write a log file in /tmp with a record of the e-mails
currtime = date.today().strftime("%B %d, %Y")
logfile = open('/tmp/email2voice.log', 'a')
logfile.write(currtime + "\n")
logfile.write("Call Number: " + callnum + "\n")
logfile.write("From: " + msgfrom + "\n")
logfile.write("To: " + msgto + "\n")
logfile.write("Subject: " + msgsubj + "\n")
logfile.write("Body: " + msgbody + "\n\n")
logfile.close()

# Convert the body of the text to a wav file
swiftcommand = "/opt/swift/bin/swift -n Millie-8kHz -o /tmp/" + callnum + ".wav '" + msgbody + "'"
os.system(swiftcommand)

# We need to allow Asterisk permission to read the wav file
chmodcommand = "chmod 777 /tmp/" + callnum + ".wav"
os.system(chmodcommand)

# Set the number to be dailed as the subject of the e-mail
OUTBOUND = msgsubj

# Send the call details to the Asteirsk Manager Interface
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
sleep(3)
s.send('Action: login\r\n')
s.send('Username: ' + USER + '\r\n')
s.send('Secret: ' + SECRET + '\r\n\r\n')
sleep(3)
s.send('Events: off\r\n\r\n')
sleep(3)
s.send('Action: originate\r\n')
s.send('Channel: Sip/' + TRUNK + '/' + OUTBOUND + '\r\n')
s.send('WaitTime: 30\r\n')
s.send('CallerId: 1234\r\n')
s.send('Application: playback\r\n')
s.send('Data: /tmp/' + callnum + '\r\n')
s.send('Context: from-internal\r\n')
s.send('Async: true\r\n')
s.send('Priority: 1\r\n\r\n')
sleep(3)
s.send('Action: Logoff\r\n\r\n')
s.close()

And that should be it. To test just send an e-mail to [email protected] with the telephone number you want to call as the subject line, and the text you want to be read in the body.

Don’t forget to write the telephone number in the format that your SIP provider is expecting it.

16 thoughts on “E-mail to voice call – with Asterisk, Postfix and Cepstral

  1. aguynutah

    how did this work outfor you?

    Is there any update to the code or improvements?

    If you could, please contact me at the supplied address as I’ve been tasked with a similar project and would like to learn from your experience.

    thank you and best regards,

    ~ct~

  2. matt Post author

    Yes, I’m still using it and it works pretty well. I use it to get important alerts and it’s been invaluable.

    I’ve not updated the code I don’t think. It might not be pretty but it works for me!

    If you’ve got any specific questions drop me a line here – http://sysadminman.net/contact.php

  3. Jens

    Hi,
    thanks a lot for this it is exactly what we need here! But it’s not quite working, asterisk writes:

    == Manager ‘rufbereitschaft’ logged on from 127.0.0.1
    [Nov 29 20:52:54] ERROR[8477]: utils.c:1109 ast_careful_fwrite: fwrite() returned error: Connection reset by peer
    == Manager ‘rufbereitschaft’ logged off from 127.0.0.1

    Do have any idea?

    Thanks
    Jens

  4. matt Post author

    Hi Jens,

    I couldn’t find anything about that specific error I’m afraid. Did you ever manage to get it sorted. Looks like PHP not able to write to the manager port. Is it on the same host (127.0.0.1)?

  5. Jens

    Hi Matt,

    thanks for the reply. No i did not manage to sort this out. Still same problem. Everything is on one box.

  6. matt Post author

    If it’s not obvious which manager command is failing you could put in some long sleep commands between each step?

  7. Jens

    I don’t see how this could help since i don’t know which single command is the problem. it just takes longer than processing the script. or did i get this wrong? (i am no programmer)
    what exactly to you mean i should enter where?

  8. matt Post author

    I was thinking that if you watched the logs (tail -f) while the commend was running you would work out where it was failing.

    Also, if you turn up logging in the Asterisk console (core set debug 15 & core set verbostity 15) you might get some more information in the logs.

  9. Jens

    I was missing “originate” in manager.conf – now it works. Thanks a lot for this, seems very good!

  10. Amit

    Hey matt..
    i am working on a exactly similar project for my Final year engineering project.
    I have a few questions. Please help me out with them.
    Our project is that one user sends an email to other and that email wil be den sent as call to the reciever.

    Noe what is the role of email server postfix in this. how can i access the email recieved by the reciever through postfix.

    Also does this project cost some money.. how will be the call generated finally.. jus give an overview…
    Thanks in advance.

  11. matt Post author

    Hi Amit,

    The type of e-mail server is not too important (postfix), you just need to be able to pipe the incoming mail to the Python script. That side of it is difficult to cover here, as there are obviously many different e-mail setups that people will have.

    Yes, there is a cost to the final call, and with the example above you already need to have an Asterisk server setup and able to make outgoing calls. There are many call providers. One example is http://voip.ms

  12. Gaurav

    hey matt,
    can u please suggest me the way in which i can pipe my emails from my gmail account to python script..?
    Pls give me detailed information about postfix, i.e. how to do this task using postfix.
    i am stuck in my project and i really need your help!!!
    so please reply…

  13. Amit

    hey matt..
    is there any other free software for text to speech conversion that i can use through the above python code…

  14. costa

    Hi Matt,
    if I want to install all the software on a vps
    Asterisk (with Astersk Manager Interface)
    E-mail server (I’m using Postfix)
    Ceptral text-to-speech (www.cepstral.com) – installed in /opt/swift/bin
    Python (I’m using v2.4.3)

    what is the minimum ram and cpu I need?

  15. Matt Post author

    Hi Costa

    I think it depends a little on what you’ve got running on the server, but mine has around 800M of RAM and probably a 2 or 3 year old CPU.

    I’d check the license for Cepstral though as you can’t do this with a single channel license any more I don’t think.

  16. costa

    Hi Matt

    I actually working on multi channel (it is a light commercial project) , talk to Cepstral, the price seems reasonable but question is the above turtorial you have on this page, apply to multi channel as well? (sorry I am newbie, hope it is not a stupid question)

Comments are closed.