Home-brew Email-to-SMS Gateway
I’ve built an Email to SMS gateway using a spare Arduino, a GSM shield and a $30 Optus pre-paid SIM card.
It can do the following:
- Convert Email to an SMS (Obviously)
- Rudimentary access control using a list of allowed senders
- Feedback if SMS conversion failed (Not authorised, incorrect number format, etc)
Source files are available at the bottom
There several different ways of achieving the same goal, but I used these parts (mainly because I already had them):
- Spare Arduino Uno
- GSM/GPRS Shield for said Arduinio
- Always-on computer (File Server running FreeBSD)
It works by running a Python script on the host computer. This script starts a connection to an IMAP server using the specified account (utilising imaplib), then polls for unread mail in the Inbox of that account every one minute. (**TODO: **imaplib2 supports IMAP IDLE, but I haven’t tried it using Python 3)
An SMS can be sent in one of two ways:
- Send an email to [email protected], with the destination number in the subject line, and SMS text in the message body:
- Send an email to [dest_number]@sms.tribalchicken.com.au, with SMS text in the Subject line:
Once SMSGateway.py receives the new email (In theory max time will be value of IMAP_CHECK_FREQ * 60), it will grab the latest (Unread) emails, decompose them into various parts and perform some validation. To be passed onto the Arduino the message must pass sender validation, and number format validation.
Sender validation is easy (though basic) – We have a flat text file called senders.txt
which maintains a list of one authorised email address per line. This is interpreted by SMSGateway.py and the “From” address is simply checked against this list
# First check if sender is allowed
# Split the email addr out of the "From" field
email = parseaddr(email_from)
if email[1] not in senders:
# Reject with a "not authorised" message
logging.warning('Sender validation: FAILED')
reject('Not authorised', email[1])
return
else:
logging.info('Sender validation: PASSED')
Obviously this could be bypassed quite easily by spoofing the From address, however since it’s such a small list containing only address under the control of my mail server, such spoofing would be rejected by the spam filter 🙂
The next step is to validate the number. Again this is easy as we’re only targeting Australian mobile numbers, which will start in +614. First is to convert the number to the international format if it is not already (We also accept numbers starting with 04, but the GSM Module will only accept international format):
# If number that is sent to us is in the format of "04xxxxxxxx", convert to "+614xxxxxxxx"
# NOTE: We are dealing with Australian numbers ONLY
if (number[:2] == '04'):
number = '+61' + number[1:]
logging.info("Destination converted to int'l format")
Next we validate the converted number using a regular expression to check the start of the number as well as length:
# Validate using regular expression
if not (re.match("(^\+[61]{2}[0-9]{9}$)", number)):
logging.warning('Destination validation: FAILED')
reject('Invalid number format (Failed regex)', email[1])
return
else:
logging.info('Destination validation: PASSED')
All too easy. Of course there is no way to tell if it’s an actual valid mobile number, but the goal of this validation is to ensure it is in a format that the GSM Module will accept.
If the number passes validation, we construct a “payload” to fire off to the Arduino for further processing (This uses the pySerial library, for compatibility):
# Construct the payload ([number] [message])
payload = str(number) + ' ' + str(text) + "\r"
# Send payload via serial to Arduino logging.info('Sending payload for delivery: %s', payload) ser.write(payload.encode())
Our SMS is now in the hands of the Arduino! At this stage all I want it to do is fire off the SMS in text mode via the SIM900 which is very easy (I believe PDU mode is generally preferred to text mode, though slightly more complex. That’s a task for another day.)
boolean process(String data) {
// Split string to number and the text
String number = data.substring(0,12);
String text = data.substring(13);
// Send AT Command to initiate SMS.
// send CMGS="[number]"\r modem.print("AT+CMGS=\"" + number + "\"\r");
// AT Response should be > prompt
if (checkATResponse(">",200)) {
// send the text to SIM900 modem.print(text);
// send CTRL-Z to end text input and send SMS
delay(50); // Added this delay for testing
modem.write(0x1A); // AT Response should be in the format "+CMGS=[msg id]" if successful
// if OK, tell computer "SEND OK")
// if not, send error from GSM module
// I have seen this take up to 18 seconds to get a negative reply - setting timeout to 20s
if (checkATResponse("+CMGS:",20000))
return true; else return false; }
else return false;
// Return false if we do not get the > prompt (Something has gone wrong, check lastGSMError }
If the GSM module is happy it will fire off the SMS and return true which will in turn, cause the Arduino to send “SEND OK” back to the host computer. If the SIM900 encounters some kind of error, that error is set in the global lastGSMError which will be send back to the computer. E.G:
ERROR: +CMS ERROR: 515
If the host machine receives an error it will inform the sender via email. If it receives “SEND OK” then no news is good news!
That’s pretty much it! I plan to add a simple socket interface to SMSGateway.py which will allow any device on the local network to simply connect and send an SMS via the gateway without having to go through the trouble of email.
A few ideas if you didn’t want to use a full blown Arduino setup:
- Simply configure the Arduinio to pass-through any Serial data.
- You could potentially cut out the middle-man and not use the Arduino at all – Either by using an RS232 GSM Module (I’ve seen them around in my travels), or if you were really
keenbored, you could try interfacing the computer directly with the Arduino-compatible GSM Module by converting your computers RS232 to TTL, via a MAX232 or similar. - You could also use an Ethernet-enabled Arduino and directly receive the email on the Arduino… However unless there is an existing library you would need to write an IMAP client yourself which would be a whole different project.
For now though, it works nicely and I can also get downtime notifications from Uptime Robot (Of course, that doesn’t help if it’s the mail server that’s gone down ;))
Source files are available here (Beware! This was a bit of a throw-together, expect errors in the code):
- Python script: SMSGateway.py
- Arduino sketch: sendsms.ino