Using Email to SMS Gateways > Example Service: Weather Updates
To show how we might use an email to SMS gateway, let's build an application that will automatically send users the weather forecast for their zip code once per day.
To register for our service, users will browse to a web page where they will provide their phone number, zip code, and the name of the carrier.
Shortly thereafter, they'll receive a text message on their mobile phone from our service that looks something like this:
We provide this code to ensure that the user isn't signing up someone else for our service. They will then enter that code onto our web page to confirm their registration.
Thereafter, once a day, at a time of our choosing (let's say 7:00 p.m. PST), the user will receive a weather forecast on her phone for the next day that will look like:
We'll also add code to allow users to stop receiving forecasts. All they have to do is reply to a forecast with the word stop.
Here's what you'll need to get started:
SMTP and IMAP access: To implement our example, you'll need a mail account with an SMTP server and an IMAP server that you can access. Many free services such as Gmail and Hotmail do not typically provide both SMTP and IMAP access. Most hosting providers do. Some free email services are starting to add IMAP support, so it may be worth checking to see if your favorite one does.Note, however, that a similar solution with POP and SMTP is of course also possible.
Web server: A web server that can execute Python CGI scripts (exactly the same as in the aggregator example). This example has quite a few files, which may make it seem a bit intimidating. Rest assured they are all quite small. Place these in the root path of your web server:
config.py
createDb.py
index.html
challenge.py
smsEmailSender.py
register_new_phone.py
verify_phone.py
get_forecast.py
do_forecasts.py
smsEmailServer.py
responseServer.py
SQLite v2 or greater: If you already have Python installed, you most likely also have SQLite. SQLite is a fast and easy to use single user SQL database. You can learn more about it here: http://www.sqlite.org/.
Scheduled tasks: Your web server also needs to have the ability to run a process at scheduled intervals. This is accomplished using cron on Unix and the Windows Task Scheduler on Windows.
The config.py file will hold configuration information for our service, such as the user name and password we use to access our SMTP server. It will also store the name of the database file with our user list in it.
Create a file called config.py with the following content and place it in your web server's root directory.
Code View: Scroll / Show All
config.py: #CHANGE THIS db_file = "database.db" # the name of the file user data is stored in server = "mail.myhost.com" # your SMTP server's URL account = "myaccount" # the user name for accessing your SMTP server password = "mypasswd" # the password for your SMTP server email_address = "myemail@mydomain.net" # the return address for outgoing email
We're going to use SQLite to store our user's data in a SQL database. Before we can use the database we'll need to set up the tables for our application. CreateDb.py will take care of this for us.
createDb.py:
Code View: Scroll / Show All
#!/usr/bin/python import sys import config from pysqlite2 import dbapi2 as sqlite tables = ["users"] table_descs = [ """ CREATE TABLE users( phonenumber VARCHAR(10), provider VARCHAR(30), zipcode VARCHAR(5), challenge VARCHAR(8), verified BOOLEAN DEFAULT FALSE, PRIMARY KEY(phonenumber) ); """, ] def main(): con = sqlite.connect(config.db_file) con = con.cursor() #sqlite2 doesn't support IF EXISTS for t in tables: try: con.execute("DROP TABLE %s" % t) except: pass for t in table_descs: print t con.execute(t) if __name__ == '__main__': main()
A complete explanation of SQL and how it works is outside the scope of this short cut. Our example should be pretty easy to understand, though. This script is set up in such a way as to allow many tables to be created, though for this example we'll only be making one.
We store the SQL command to create the users table in the table_descs list. It's a simple table with a few strings, including the user's phone number, provider, and zip code. The verified field lets us know whether or not the user has validated that they actually have the phone with the phone number they registered (recall the validation step from the example). We set phonenumber to be the primary key because we know it will be unique, and lookups will often be done by phone number.
Once you upload this script to your server, you'll want to run it (just like you run other python scripts). The rest of the code connects to a SQLite database with the name we set up in config.py and then executes our CREATE TABLE command.
It's important that any service that pushes SMSs to users allows its users to sign-up and remove themselves from that service. Our service will let the user sign-up by providing his phone number, provider, and zip code.
Code View: Scroll / Show All
index.html: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html> <head> <title>Weather Updates</title> </head> <body> <p>First, register your phone</p> <p>Then confirm it by typing the confirmation code into the form</p> <p>You'll receive a weather forecast for the next day, every day</p> <hr/> <h3> Register </h3> <form action="register_new_phone.py" method="get"> <p>Phone Number (10 Digit US only): <input type="text" name="phonenumber" size="10"/></p> <p>Zip: <input type="text" name="zip" size="5"/></p> <p>Provider: <select name="provider"> <option value="tmobile">T-Mobile</option> <option value="cingular">Cingular/AT&T</option> <option value="verizon">Verizon</option> <option value="sprint">Sprint</option> <option value="nextel">Nextel</option> </select> </p> <p><input type="submit" value="Register"/></p> </form> <hr/> <h3> Verify </h3> <form action="verify_phone.py" method="get"> <p>Phone Number: <input type="text" name="phonenumber" size="10"/></p> <p>Code: <input type="text" name="code" size="8"/></p> <p><input type="submit" value="Verify"/></p> </form> <hr/> </body> </html>
There are two forms on this page. The first one allows initial registration, which will send a code to the entered phone number. The second one allows the user to verify that he owns the phone he entered by entering the secret code and his phone number.
You'll notice that the form action for the first form is register_new_phone.py and the action for the second is verify_phone.py. Registration requires that we perform two additional tasks that we haven't covered yet:
Generate a unique code to send to the user
Send an SMS to the user via email
We'll cover these tasks first, and then tie them all together to register a phone.
There are a lot of mechanisms that could be used to generate unique codes. In order to be useful for SMS, codes need to be:
Composed of a short sequence of alphanumeric characters
Long enough that they can't be guessed
For our application, we're SMS'ing a code to the user's phone and having him enter it on a web page. You might think it should be easier to display a code to the user on the web page itself and have the user SMS it to you. However, this approach can't be used when using email to implement an SMS service because users can't typically can't initiate communication with an email service from SMS.
Challenge.py will create our verification code.
challenge.py: import sys import random import sha CHALLENGE_LENGTH = 8 def generate_challenge(): nonce = str(random.random()) challenge = sha.sha(nonce).hexdigest()[0:CHALLENGE_LENGTH] return challenge
First, we import random and sha. The challenge length is set to be 8 characters.
One function, generate_challenge is defined. The function takes the user's phone number as an input. First, it generates a random number and converts it to a string. Then it uses the sha algorithm to generate a cryptographic hash of the random number, gets the result in hex, and truncates the hash to the first 8 characters.
There are a many mechanisms for generating user readable characters from the random number (including just passing the random number to the user). We use a cryptographic hash to help protect against the compromise of a weak random number generator.
The challenge is returned to the caller after its generated.
This is the part most of you have probably been waiting for. We're going to connect to our SMTP server and send SMSs using the carriers' SMTP gateways.
Code View: Scroll / Show All
smsEmailSender.py: import sys import smtplib emails = { "tmobile" : "tmomail.net", "cingular" : "mobile.mycingular.net", "verizon" : "vtext.com", "sprint" : "messaging.sprintpcs.com", "nextel" : 'messaging.nextel.com', } class SmsEmailSender(object): def __init__(self, server, account, password, from_addr): self.__smtp = smtplib.SMTP() self.__account = account self.__password = password self.__from_address = from_addr self.__server = server def sendsms(self, phone_number, provider, body, from_addr = None): if not from_addr: from_addr = self.__from_address self.__smtp = smtplib.SMTP() self.__smtp.connect(self.__server) self.__smtp.starttls() self.__smtp.login(self.__account, self.__password) self.__smtp.sendmail( from_addr, "%s@%s" % (phone_number, emails[provider]), "%s" % (body) )
First, we define a dictionary that maps user-friendly provider names to the mail address suffixes for those providers. A nearly complete database of these mappings is available through the CoreSMS project (http://www.corelevel.com/sms/).
SmsEmailSender is defined as a class to allow easier reuse. The SMTP connection could also be reused, but in our class we recreate it each time we call sendmail, as the error handling for the other case is more complicated.
SmsEmailSender takes four parameters in its constructor:
server: The SMTP server to connect to
account: The account name on the SMTP server
password: The password for the account
from_addr: The FROM address to use on outgoing mail
We could use these values from our configuration and not require them to be passed in. SmsEmailSender is designed to be independent of configuration mechanisms and parameters so that it can be reused in other projects.
Our class has only one function, sendsms. The function takes five parameters:
phone_number: The phone number to send to.
provider: The provider to use, which must be in the emails dictionary defined at the top or an exception will be thrown.
body: The text of the email.
from_addr [optional]: An optional FROM address that will override the one used when constructing the object.
Next we connect to the server and log in. Calling starttls before sending your password ensures that it will be transmitted encrypted over the wire, instead of in plaintext (the default for SMTP).
The actual SMTP sendmail function simply requires you to pass it the address that the email will be from, the address you are sending the mail to, and the entire body of the message.
Our form submits a GET request to a CGI script register_new_phone.py, passing parameters describing the user. The script generates a unique code.
register_new_phone.py:
Code View: Scroll / Show All
#!/usr/bin/python import sys import re import cgi import cgitb; cgitb.enable() import smsEmailSender import challenge import config from pysqlite2 import dbapi2 as sqlite def result(text): print "Content-type: text/html\n\n" print """ <html> <head><title>Registration Code Sent</title></head> <meta http-equiv="refresh" content="5; URL=/"> <body> %s </body> </html> """ % text sys.exit(0) form = cgi.FieldStorage() phonenumber = form.getfirst("phonenumber") zipcode = form.getfirst("zip") provider = form.getfirst("provider") if not phonenumber or len(phonenumber) > 10: result("Please enter a valid 10 digit phone number") phonenumber = phonenumber[0:10] if not re.match("[0-9]{10}", phonenumber): result("Please enter a valid 10 digit phone number") if not zipcode or len(zipcode) > 5 or not re.match("[0-9]{5}", zipcode): result("Please enter a valid 5 digit zipcode") if not provider in smsEmailSender.emails: result("Your cell phone provider is not yet supported.") con = sqlite.connect(config.db_file) cur = con.cursor() serv = smsEmailSender.SmsEmailSender( config.server, config.account, config.password, config.email_address ) cur.execute("SELECT * FROM users WHERE phonenumber = ?", [phonenumber]) res = cur.fetchone() if res: if res[4]: result("This phone has already been registered and verified") serv.sendsms(res[0], res[1], "Your code is: %s" % res[3]) result("That number has already been registered, resending code") else: challenge = challenge.generate_challenge() cur.execute("INSERT INTO users VALUES(?,?,?,?,?)", [phonenumber, provider, zipcode, challenge, False]) cur.close() con.commit() serv.sendsms(phonenumber, provider, "Your code is: %s" % challenge) result( """ <p> The registration code has been sent to your phone. </p> <p> Please return to the home page and enter it to verify your phone number. </p> <p> Redirecting in 5 seconds...</p> """)
There's quite a bit of code here. The first part of this script is identical to the one described in the aggregator chapter. We import needed modules, define our function to display results to the user, and we grab our three parameters from the query (phonennumber, zip,provider).
The next three blocks of conditionals validates that all the input parameters are like we expect them to be:
phonenumber: should be exactly 10 digits
zipcode: should be exactly 5 digits
provider: must be in the list of smsEmailSender.py's supported list of providers
If any of these conditions aren't met, we call result and return an informative error message to the user.
If everything checks out, we go ahead and open a connection to our database and get a cursor object. Cursor objects are transactions in Python's database API.
We instantiate a new SmsEmailSender object because we'll be using it in a few lines.
If the user is already registered, we want to let them know that and not reregister him, so we use our database connection to look for a user with the phone number that was passed onto us.
If we get a result, we do one of two things:
If the user isn't verified yet, we resend his confirmation code (he may have lost it).
If the user is already verified, we inform him that he is in fact already registered and good to go.
If the user isn't in the database, we generate a new challenge for him, and insert a new record into the database. It's important to call commit on cursor objects if they change a database's state, otherwise the changes won't have any effect.
Now we send the user his registration code via the sendsms method we wrote earlier. We call result and inform him that the code has been sent to his phone. As a courtesy to the user, our result HTML has a meta-refresh directive in it that will send him back to the home page in five seconds.
Once the user receives his registration code, he'll enter it into the form on the front page.
verify_phone.py:
Code View: Scroll / Show All
#!/usr/bin/python import sys import re import cgi import cgitb; cgitb.enable() import config from pysqlite2 import dbapi2 as sqlite def result(text): print "Content-type: text/html\n\n" print """ <html> <head><title>Verified</title></head> <meta http-equiv="refresh" content="5; URL=/"> <body> %s </body> </html> """ % text sys.exit(0) form = cgi.FieldStorage() phonenumber = form.getfirst("phonenumber") code = form.getfirst("code") if not phonenumber or len(phonenumber) > 10: result("Please enter a valid 10 digit phone number") phonenumber = phonenumber[0:10] if not re.match("[0-9]{10}", phonenumber): result("Please enter a valid 10 digit phone number") if not code or len(code) != 8: result("Invalid Code") con = sqlite.connect(config.db_file) cur = con.cursor() cur.execute("SELECT * FROM users WHERE phonenumber = ?", [phonenumber]) res = cur.fetchone() if res and code == res[3]: cur.execute("UPDATE users SET verified = 'True' WHERE phonenumber = ?", [phonenumber]) con.commit() result("<p> Success - Phone Registered </p>") else: result("Invalid Code")
Just as in the last CGI script, we import needed libraries, define our result function, and get our two parameters from the form: phonenumber and code. We then validate our parameters.
If our parameters are valid we connect to the database and look for a record with the phone number of the one that was passed in. If there is no record or the record doesn't have the same challenge code stored in it as the user passed to us, we error out.
If the code matches, we update our database by setting the verified field to True. A message indicating success is returned to the user.
Now that we have registered and verified users, we need to get forecasts for them. Luckily for us, Yahoo has a great RSS API for retrieving weather forecasts at (http://developer.yahoo.com/weather/).
Our code talks to the web service defined there and grabs tomorrow's forecast:
get_forecast.py:
Code View: Scroll / Show All
import re import urllib from xml.dom.minidom import parse yweather_ns = "http://xml.weather.yahoo.com/ns/rss/1.0" def get_forecast(zipcode): res = urllib.urlopen("http://weather.yahooapis.com/ forecastrss?p=%s&u=f"%zipcode) dom = parse(res) fdict = {} #get the location loc = dom.getElementsByTagNameNS(yweather_ns, "location")[0] fdict["city"] = loc.getAttribute("city") fdict["state"] = loc.getAttribute("region") #get tomorrow's forecast forecast = dom.getElementsByTagNameNS(yweather_ns,"forecast")[1] fdict["date"] = forecast.getAttribute("date") fdict["high"] = forecast.getAttribute("high") fdict["low"] = forecast.getAttribute("low") fdict["text"] = forecast.getAttribute("text") return fdict if __name__ == "__main__": get_forecast("98112")
We use the python module minidom to handle XML processing. We define one function, get_forecast that takes the zip code for which to obtain forecast data.
The function first fetches the forecast (using urllib) and then uses the parse command of minidom to create a DOM.
Next, we look up the location element and grab the city and state of our forecast and put them into a dictionary.
There are two forecasts included with each request today's and tomorrow's. Our service gives people the next day's weather in the evening the day before, so we want to grab tomorrow's forecast.
We retrieve a list of all the forecast nodes and grab the second one (index '0' is the first forecast). That element is used to get the date of the forecast, the high and low temperatures, and a textual description of the expected conditions. All of these elements are added into a dictionary that is then returned to the caller.
Now that we can get a forecast, and we know who wants forecasts for which locations, we need to write a script that will loop through our users, get their forecasts, and send them.
do_forecasts.py:
Code View: Scroll / Show All
#!/usr/bin/python import sys import re import smsEmailSender import config from get_forecast import * from pysqlite2 import dbapi2 as sqlite con = sqlite.connect(config.db_file) serv = smsEmailSender.SmsEmailSender( config.server, config.account, config.password, config.email_address ) cur = con.cursor() users = cur.execute("SELECT phonenumber, provider, zipcode FROM users WHERE verified='True'") for u in users.fetchall(): fd = get_forecast(u[2]) forecast = "Weather for %(city)s, %(state)s on %(date)s will be %(text)s with High/Low of %(high)s/%(low)s" % fd serv.sendsms(u[0], u[1], "Daily Forecast", forecast)
The first thing we do here is import the modules we need, including our own smsEmailSender, config, and get_forecast modules.
Next we connect to our database and instantiate an SmsEmailSender. A cursor object is created and used to find all the users whose verified flag is set to True.
The fetchall command is used to retrieve a list of users that we will iterate over. For each user, we get a forecast for his zip code. The zip code is stored in the third element (index 2) of the returned row, because that's how we asked for it to be returned in our SELECT statement.
Next we use Python's string formatting to create a text string that looks like the one in the example at the start. The slightly different form of string formatting used here allows you to explicitly grab elements of a dictionary instead of relying on positioning alone.
After creating the message, we use our SmsEmailSenderinstance, calling its sendsms member with the phone number (element 0 of the returned row), the provider (element 1), a subject of "Daily Forecast," and the text of our forecast.
This script will loop through all of our users and SMS them tomorrow's forecast.
Now we need to get do_forecasts.py to run once a day. On Unix you'll type:
crontab -e
This will open your crontab file in an editor (probably vi, but you can change your editor by setting the EDITOR shell variable) and make it look like this:
SHELL=/bin/bash PATH=/bin:/usr/bin:/usr/local/bin 00 19 * * * cd WEBDIR; python do_forecasts.py
Replace WEBDIR with the absolute path to the directory where all the files from this project are placed. This will cause your script to be run once a day at 7:00 p.m. (19:00). If you want it to run at a different time in order to test it, just change the "00 19" to be the 24-hour time, minute first, of when you want it to run.
If you're implementing this service on Windows, you can use the Windows Task Scheduler to implement something similar.
Wow. That was a decent bit of code, but we've implemented our first SMTP-based SMS service.
While most carriers don't enable initiating a conversation with an email service from SMS, most do allow users to respond. This can be useful for your service. For example, you may wish to allow users to quit via SMS, or to ask for the forecast at some time other than that scheduled.
To demonstrate how we would go about receiving responses from SMS via email, we'll create code to allow the user to stop receiving forecasts by replying "stop." This is a little more complicated than the previous bits of code we've gone over, as it uses threading. It relies on our config.py and requires that the same account you use to send mail can be used to access an IMAP server.
First, we'll create a class that will poll for new messages and dispatch them to a callback when they are received.
smsEmailServer.py:
Code View: Scroll / Show All
#!/usr/bin/python import sys import time import imaplib import threading import email import traceback class SmsEmailServer(object): def __init__(self, server, account, password, on_email_func, polling_interval = 10): self.__account = account self.__password = password self.__on_email = on_email_func self.__polling_interval = polling_interval self.__running = False self.__server = server self.__imap = imaplib.IMAP4_SSL(server) self.__running = True self.__poll_thread = threading.Thread(target = self.__poll_mail_server) self.__poll_thread.start() def shutdown(self): if self.__running: self.__running = False self.__poll_thread.join() def __poll_mail_server(self): while (self.__running): try: self.__imap = imaplib.IMAP4_SSL(self.__server) self.__imap.login(self.__account, self.__password) self.__imap.select() typ, data = self.__imap.search(None, 'ALL') for num in data[0].split(): typ, data = self.__imap.fetch(num, '(RFC822)') m = email.message_from_string(data[0][1]) self.__on_email(m) self.__imap.store(num, "+FLAGS", "(\\Deleted)") self.__imap.expunge() time.sleep(self.__polling_interval) except: traceback.print_exc() self.__imap.logout()
We start by importing modules that we need, most notably: imaplib,threading, and email.
We then define a class called SmsEmailServer. It takes the standard server, account, and password parameters that SmsEmailSender used.
It takes a function as a parameter for on_email_func.This is a function that will be called whenever an email message is received (which is what SMS replies look like an email).
It also takes an optional polling_interval parameter, which determines how often it looks for new mail. The default poll interval is 10 seconds.
SmsEmailServer sets a bunch of variables that it will use inside its polling thread, launches the polling thread, and returns.
The polling thread runs the function __poll_mail_server. This function has a loop that will run forever unless shutdown is called from another thread (ResponseServer.py, in this example).
Inside the loop we connect to the IMAP server over SSL and login. The IMAP connection can be reused, but it makes the code more complicated, so it is not presented here.
The rest of the loop is esoteric IMAP specific code that you needn't worry about, except for the part that creates a new email object and calls on_email_func with results.
After that function is called our code flags the message for deletion to make sure we don't process it again.
Being able to receive emails alone doesn't help us interact with our users. We'll need to process them. This script creates a new SmsEmailServer to listen for incoming SMSs. When it gets one it checks to see if the first word is "stop." If it is, we'll see if the sender is one of our users and remove them from the system
response_server.py:
Code View: Scroll / Show All
#!/usr/bin/python import sys import time import config from smsEmailServer import * from smsEmailSender import * from pysqlite2 import dbapi2 as sqlite def on_email(email): contents = None #some phones send mms' as responses phonenumber = email["From"].split("@")[0].strip() if email.is_multipart(): for p in email.get_payload(): #we only support plain text... if p.get_content_type() == "text/plain": contents = p.get_payload() break else: contents = email.get_payload() if contents[0:4].lower() == "stop": print "Got stop from: %s" % phonenumber con = sqlite.connect(config.db_file) cur = con.cursor() cur.execute("SELECT * FROM users WHERE phonenumber = ?", [phonenumber]) u = cur.fetchone() if u: cur.execute("DELETE FROM users WHERE phonenumber = ?", [phonenumber]) cur.close() con.commit() serv = SmsEmailSender( config.server, config.account, config.password, config.email_address ) serv.sendsms(u[0], u[1], "You'll no longer receive forecasts from us.") print "User Removed" else: print "No registered user with this phonenumber" else: print "Received Unknown Command: %s" % contents def main(): se = SmsEmailServer(config.server, config.account, config.password, on_email) try: while True: time.sleep(.5) except: print "\n Shutting Down..." se.shutdown() if __name__ == '__main__': main()
The main function just creates a new SmsEmailServer and sits in a loop hitting Ctrl-C (or Ctrl-Break) will stop the process. When it creates the server it registers the on_email function as the handler for received email. SmsEmailServer will call our on_email function every time it gets a message.
The on_email function looks kind of complicated. The code at the start of our function grabs the From address out of the message. This is typically the phone number of the originating user. However, some phones, such as the T-Mobile Sidekick, don't behave this way. Instead, they send mail from an email address account associated with the phone. See the 'Response addresses' blurb at the bottom of this chapter for a method to deal with this.
To further complicate things, some phones send MMSs as responses to SMSs sent via email. This is a bit easier for us to deal with, however, because we still know who the message is from, it just happens to be in an unexpected format. MMS's are multipart MIME messages. So, after our code gets the From address, it checks to see if the message is multipart. If it is, it looks for the plain text portion and considers that the message content.
Next it looks to see if "stop" is the first word. If it isn't, we bail out and do nothing (add your own custom handling here; for example, you can allow users to look up the forecast for their city).
If it is "stop," we have a bit of work to do. First we connect to the database and look for a user with a phone number equal to the From address. If there's a match, we remove the entry from the database. Right after we remove the entry, we send a confirmation SMS to the user who asked to be removed.
If we can't find a user with a phone number equal to the From address, we simply do nothing.
In order to receive SMSs, you'll need to leave this script running on a server. The machine needs to be the same one that the web frontend is running on, because the database is stored in a file. Contact your hosting provider for information on how to set up a background process, as it varies from provider to provider.
NOTE
The address each SMS appears to be from is an important consideration when designing your service. The user will always see the From address at the start of every message, and any responses will be delivered to that address.
For example, if you send mail to 2065551212@tmomail.net from myservice@mydomain.com, your user will receive an SMS that looks something like:
Conversely, when your service receives a reply from an SMS user, you'll want to know who sent you the message.
The typical way to do this is to simply look at the address of the sender when checking mail for the service. Most emails will appear to be from something similar to 1112223333@carrier.net. You could strip the number of the front portion, verify that the phone number is a registered user, and respond as appropriate given the content of the request.
This approach works for most services, but it's less than ideal for a couple of reasons:
It allows hackers to impersonate other users by forging From addresses on emails.
If you have sent multiple messages to the user, there may be no way to know which message the user is responding to.
As an alternative, you may pick a unique From address for every outgoing message or conversation and store it in a table. In this sense, the unique address is exactly like a session key on a web site. This approach has several advantages:
Impersonating a user is more difficult, providing you check that the From address for a message matches the expected From address.
It reduces the likelihood you will be throttled based on From addresses. There are many other heuristics that could be used by carriers to throttle, so this is not a complete solution (some carriers throttle by the IP address of the SMTP server, for example).
It enables threading, as each From address can correspond to a separate conversation with the user.