Sending SMS Notifications using Twilio, Django and the Heroku Scheduler
For my Twilio SMS Demo App this month, I wanted to send notifications to LinkedIn members based on hot items for them in LinkedIn Today. This is a tutorial on creating a python script to access user information in the Django database, pull information from a Web Service (in this case the LinkedIn Today API) and send an SMS to the user. A couple of caveats are in order here to make sure that people don’t accidentally run afoul of the LinkedIn API Terms of Use when implementing something of this sort.
- The LinkedIn Today API is only available internally at this point, and should only be used as an example of what’s possible
- I have stored the user’s phone number in the database – when storing LinkedIn information you always need to notify the user that you’re doing that, explicitly.
- A working Django system on Heroku. If you set up my example LinkedIn Django application from this post, you’ll be able to extend that here.
- A Twilio account – ideally with a purchased number, but you could get this system to work with the sandbox system. You get $30 in Twilio credit to start out with, which is plenty to set up a demo of this sort.
- A Google API key (this is optional, but it’ll get you more shortened links)
Install the Twilio Library
% pip install twilio % pip freeze > requirements.txt
Adding the Phone Number
Remember, if you’re going to store any information at all about a LinkedIn member, you need to explicitly tell them so. That having been said, you can add the following to your LinkedIn application to get the phone number and store it. I’m not going to provide the completed code to do this since you need to implement some user notification if you’re using this in a production system.
linkedin/models.py
class UserProfile(models.Model): user = models.ForeignKey(User) oauth_token = models.CharField(max_length=200) oauth_secret = models.CharField(max_length=200) phone_number = models.CharField(max_length=10)
linkedin/views.py
Modify the LinkedIn profile URL:
url = "http://api.linkedin.com/v1/people/~:(id,first-name,last-name,industry,phone-numbers)"
Add this logic before saving the user profile:
if "phoneNumbers" in profile: for phone_number in profile['phoneNumbers']['values']: userprofile.phone_number = re.sub(r"\D","",phone_number['phoneNumber']) if phone_number['phoneType'] == 'mobile': continue
If you’ve already created your DB you’ll need to wipe it out and recreate it with these new models in order for this to work. You can most easily do this by just starting a new project for this version – while you can drop the database locally and recreate it with syncdb, for heroku you’ll need to create a new cedar project so a new database will be created.
Creating the Job
Now we’ll walk through the code. This file wants to live at your LinkedIn project level (so in the example, in the hellodjango directory). It’s also available in the GitHub repository for django-linkedin-simple.
First, do some basic setup:
from django.core.management import setup_environ import settings setup_environ(settings)
from twilio.rest import TwilioRestClient import oauth2 as oauth import time import simplejson import datetime import httplib2 import psycopg2 account = "TWILIO_ACCT_NUMBER" token = "TWILIO_TOKEN" twilioclient = TwilioRestClient(account, token) consumer = oauth.Consumer( key="LINKEDIN_CONSUMER_KEY", secret="LINKEDIN_CONSUMER_SECRET") url = "http://api.linkedin.com/v1/people/~/topics:(description,id,topic-stories:(topic-articles:(relevance-data,article-content:(id,title,resolved-url))))"
Grab the user profiles (this includes the user tokens and secrets)
users = User.objects.all()
Iterate over the users, grabbing their token, secret, django userid and phone number, then grab the LinkedIn Today feed for that user.
for djangouser in users: if djangouser.username == "admin": continue profile = UserProfile.objects.get(user=djangouser) token = oauth.Token( key=profile.oauth_token, secret=profile.oauth_secret) phone = profile.phone_number userid = profile.user_id user_articles = SentArticle.objects.filter(user=djangouser) # Now make the LinkedIn today call and get the articles in question client = oauth.Client(consumer, token) resp, content = client.request(url, headers={"x-li-format":'json'}) results = simplejson.loads(content)
We only want to send alerts for articles which have a high “score”, indicating that they are currently hot topics for this user. Look through the articles to find those “hot topics”:
for topic in results['values']: for story in topic['topicStories']['values']: for article in story['topicArticles']['values']: score = article['relevanceData']['score'] if score > 6:
Now, SMS messages are expensive for us (the application owner) and potentially for the user, so we only want to tell the user if they haven’t already heard about this article.
checkarticle = user_articles.filter(article_number__exact=article['articleContent']['id'])
If there aren’t any matching articles for this user, go ahead and send them a message, then log it in the database so we don’t tell them again later:
if len(checkarticle) == 0: # This is where we get the shortened URL from google because LinkedIn doesn't provide one http = httplib2.Http() body = {"longUrl": article['articleContent']['resolvedUrl']} resp,content = http.request("https://www.googleapis.com/urlshortener/v1/url?key=YOUR_GOOGLE_API_KEY","POST",body=simplejson.dumps(body),headers={"Content-Type":"application/json"}) googleresponse = simplejson.loads(content) sentarticle = SentArticle(article_number=article['articleContent']['id'],user=djangouser,timestamp=datetime.datetime.today()) sentarticle.save() bodytext = article['articleContent']['title'] + " " + googleresponse['id'] bodytext += " ('save %s')" % sentarticle.id message = twilioclient.sms.messages.create(to="+1" + phone, from_="+YOUR_TWILIO_NUMBER", body=bodytext)
Great, we’ve got an application that’ll send messages for the users in the Django system when something hot is there to talk about.
Pushing to Heroku
Now we need the job to work on Heroku, and get scheduled. If you went through my previous post using the existing GitHub example, the file is already up at Heroku, otherwise you’ll need to push the file to your Heroku instance:
% git add hellodjango/sendArticles.py % git push heroku master
Whether it was already there or you just put it there, you should check to make sure that it runs correctly. Make sure you’ve logged into the system using your LinkedIn ID and stored your phone number there.
% heroku run python hellodjango/sendArticles.py
Did it work? Great!
Schedule it in Heroku
The Heroku Scheduler is a free add-on, but remember that since it uses machine time it can start racking up the charges if you don’t keep an eye on it. You can add it using the command line, or you can add it under “Resources” for your application on the Heroku site. Once you’ve added it you can manage it using the scheduler dashboard.
Pick whatever frequency you like, and put “python hellodjango/sendArticles.py” in the field, and you’re done.
Don’t forget to stop the scheduler when you’re no longer testing so you don’t get a surprise bill from Heroku later.
The next post will cover handling POST requests from Twilio in response to SMS replies from your users.
GEEK STUFF · HEROKU · LINKEDIN · PYTHON · WEB APIS
django heroku linkedin python twilio