JeanCarl's Adventures

Project 1: Remind Me with Twilio

June 12, 2015 |

If I’m supposed to keep a schedule of when each project is supposed to start and end, I’m going to need some reminders. How about we set up a SMS reminder system? Here’s my thought process in planning out what should happen.

First, a user will register a number and be able to set up one-time reminders. Each reminder will have a specified time it should be sent, a message, and a destination phone number.

For simplicity, we’ll restrict the time to be on the minute level. That way, we can run a task once a minute, check for new reminders to be sent, process them, and wait until the next interval. If no messages meet that criteria, nothing will be done.

Some logistics I’m thinking about:

SMS message length limit is 160 characters. Even though Twilio allows up to 1600 characters, spliced into 160 character message chunks, for simplicity, the message length should be limited to 160 characters.

This is inherently going to be restricted to a small use case since Twilio limits who can receive SMS messages from trial accounts.

Can I run a cron job to process messages? Can I use a purely JavaScript intervals using setInterval?

The Setup

I’m using a basic Ubuntu 14.04 webserver. Let’s start with setting up Node.js and MongoDB. AngularJS and JQuery Mobile will use CDN versions.

MongoDB will store the reminders, AngularJS will receive the user’s information, and Node.js will be the glue between. JQuery Mobile will provide a quick UI framework to make it look decent.

Node.js

To install Node.js:

sudo apt-get update
sudo apt-get install nodejs
sudo apt-get install npm

Some version info:

# nodejs -v
v0.10.25

# npm -v
1.3.10

Twilio

I’m really eager to test out Node.js and see if Twilio is simple to use on this platform! Let’s install the Node.js helper library Twilio has provided.

npm install twilio

Let’s create a simple test to make sure things are working right. Create a twiliotest.js file.

// Filename: twiliotest.js
// Your accountSid and authToken from twilio.com/user/account
var accountSid = "ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
var authToken = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";

// Replace with path to the node module (i.e. /usr/local/lib/node_modules/twilio)
var client = require('twilio')(accountSid, authToken);

client.messages.create({
	body: "Don’t forget to finish project 2!",
	to: "+15555555555", // Change to your verified phone number
	from: "+15555555555" // Change to Twilio phone number
}, function(err, message) {
	process.stdout.write(message.sid);
});

Run it from the command line to make sure the code works as expected.

nodejs twiliotest.js

And you should see a message id come back if successful:

# nodejs twiliotest.js
SM5XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

After successfully checking that the SMS is sent, we’re on the way to making this idea a reality. Yay!

MongoDB

Next thing we need is a place to store these messages until they are sent. We’ll use MongoDB to store JSON objects that represent each message. To install MongoDB, run this command:

apt-get install mongodb

To check that it is running, run this command:

service mongodb status

AngularJS

We’re going to use a CDN version of AngularJS for simplicity. You can find one hosted by Google here.

I’m using https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js.

JQuery Mobile

To make a quick UI, we’re going to use a stock JQuery Mobile template using the libraries hosted by a CDN.

Remind Me!

We have four files, server.js, sendreminders.js, index.html, and reminders.js.

// Filename: server.js
var mongooseServerAddress = 'mongodb://127.0.0.1:27017/test';

var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var mongoose = require('mongoose');

app.use(bodyParser.json());
app.listen(8080);

console.log("App listening on port 8080");

mongoose.connect(mongooseServerAddress);

var Reminder = mongoose.model('Reminder', {
    text: String,
    time: Number,
    phonenumber: String
});

// Returns reminders associated with the given phone number.
app.get('/api/reminders', function(req, res) {
	var url = require('url');
	var url_parts = url.parse(req.url, true);
	var query = url_parts.query;

	Reminder.find({phonenumber: query.phonenumber}, function(err, reminders) {
        if (err)
            res.send(err);

        res.json(reminders);
    });
});

// Adds a reminder for a specific phon number.
app.post('/api/reminders', function(req, res) {
	var r = Reminder.create({
            text: req.body.text,
            time: req.body.time,
            phonenumber: req.body.phonenumber
        }, function(err, reminder) {
            if (err)
                res.send(err);
            
            res.send(reminder);
        });
});

// Removes a reminder.
app.post('/api/reminders/remove', function(req, res) {
	Reminder.remove({_id: req.body.reminderId}, function(err, reminder) {
    if (err)
        res.send(err);            
	});
});

app.use(express.static(__dirname + '/public'));
// Filename: sendreminders.js

// Twilio Account SID
var twilioAccountSid = 'ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';

// Twilio Auth Token
var twilioAuthToken = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";

// Twilio phone number to send SMS from.
var twilioNumber = 'XXXXXXXXXX';

// Set to how frequently the queue should be checked.
var frequencyMilliseconds = 5000;

// Mongo DB server address
var mongooseServerAddress = 'mongodb://127.0.0.1:27017/test';

/*********** End Configuration ***********/

var client = require('twilio')(twilioAccountSid, twilioAuthToken);
var mongoose = require('mongoose');

mongoose.connect(mongooseServerAddress);

var Reminder = mongoose.model('Reminder', {
    text: String,
    time: Number,
    phonenumber: String
});

setInterval(function()
{
	var timeNow = new Date();
	console.log(Math.floor(timeNow.getTime()));

	// Find any reminders that have already passed, process them, and remove them from the queue.
	Reminder.find({"time": {$lt: Math.floor(timeNow.getTime())}}, function(err, reminders)
	{
		if(err)	{
			console.log(err);
			return;
		}

		if(reminders.length == 0)
		{
			console.log('no messages to be sent');
			return;
		}

		reminders.forEach(function(message)
		{
			client.messages.create({
			    body: message.text,
			    to: "+1"+message.phonenumber,
			    from: "+1"+twilioNumber
			}, function(err, sms) {
				if(err)
					console.log(err);

				console.log('sending '+message.text+' to '+message.phonenumber);
			    process.stdout.write(sms.sid);
			});
			
			Reminder.remove({_id: message._id}, function(err)
			{
				console.log(err)
			});
		});
	});
}, frequencyMilliseconds);
<!-- Filename: index.html -->
<!doctype html>

<!DOCTYPE html> 
<html ng-app="reminderApp">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1"> 
  <title>Remind Me!</title> 
  <link rel="stylesheet" href="http://code.jquery.com/mobile/1.1.1/jquery.mobile.1.1.min.css" />
  <script src="http://code.jquery.com/jquery-1.7.1.min.js"></script>
  <script src="http://code.jquery.com/mobile/1.1.1/jquery.mobile-1.1.1.min.js"></script>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.16/angular.min.js"></script>
    <script src="reminder.js"></script>
</head> 

<body> 

<div data-role="page">

  <div data-role="header" data-theme="b">
    ## Remind Me!
  </div><!-- /header -->

  <div data-role="content"> 
    <div ng-controller="ReminderListController as reminderList">
      <h3>My Phone #</h3>
      <input type="text" ng-model="reminderList.phonenumber" ng-blur="reminderList.fetchList()" />
      
      <h3>My Reminders <div ng-show="reminderList.syncing">Syncing...</div></h3>
      <ul data-role="listview" data-inset="true" data-split-icon="delete" data-split-theme="d">
        <li ng-repeat="reminder in reminderList.reminders | orderBy:'time'">
          <a href="#"><h2>{{reminder.text}}</h2>
          <p>{{reminder.time | date:"MM/dd hh:mm a"}}</p>
          <a href="#" ng-click="reminderList.removeReminder(reminder)" data-rel="popup" data-position-to="window" data-transition="pop">Remove Reminder</a>
        </a>
        </li>
      </ul>
      <div ng-hide="reminderList.reminders.length > 0">
        You have no reminders!
      </div>
      
      <form ng-show="reminderList.phonenumber.length == 10">
        <h3>New Reminder</h3>
        <input type="text" ng-model="reminderList.reminderText"  size="30"
               placeholder="remind me about..."> 
        <div class="ui-grid-a">
          <div class="ui-block-a">
            on<br />
            <input type="date" ng-model="reminderList.reminderDate" />
          </div>
          <div class="ui-block-b">
            at<br />
            <input type="time" ng-model="reminderList.reminderTime"/>
          </div>
        </div>
        <input class="btn-primary" type="button" ng-click="reminderList.addReminder()" value="Add">
      </form>
    </div>
  </div><!-- /content -->
  
  <div data-role="footer" data-theme="b">
    <h4>Copyright (c) JeanCarl Bisson</h4>
  </div><!-- /footer -->
  
</div><!-- /page -->

</body>
</html>
// Filename: reminders.js

angular.module('reminderApp', [])
  .controller('ReminderListController', function($scope, $http, $filter) {
    var reminderList = this;

    reminderList.reminderDate = new Date();
    reminderList.reminderTime = new Date();
    reminderList.reminderTime.setMilliseconds(0);
    reminderList.reminderTime.setSeconds(0);
    reminderList.syncing = false;

    reminderList.reminders = [];

    // Get reminders from the server.
    reminderList.fetchList = function() {
      reminderList.syncing = true;
      
      $http.get('/api/reminders?phonenumber='+reminderList.phonenumber).success(function(response) {
        reminderList.reminders = response;
        reminderList.syncing = false;
      });
    };
 
    reminderList.addReminder = function() {
      var reminderDateTime = new Date($filter('date')(reminderList.reminderDate, 'yyyy-MM-dd')+" "+$filter('date')(reminderList.reminderTime, 'HH:mm'));

      var data = {
        text: reminderList.reminderText, 
        time: reminderDateTime.getTime(), 
        phonenumber: reminderList.phonenumber,
        done:false
      };

      $http.post('/api/reminders', data).success(function(response) {
        reminderList.reminders.push(response);
        reminderList.reminderText = '';
      });
    };
 
    reminderList.removeReminder = function(reminder) {
      var oldReminders = reminderList.reminders;
      console.log(reminder);
      reminderList.reminders = [];
      angular.forEach(oldReminders, function(r) {
        if(reminder._id != r._id) {
          reminderList.reminders.push(r);
        }
      });

      $http.post('/api/reminders/remove', {reminderId: reminder._id});
    };
  });

Still with me? Great! Here’s some things to note. sendreminders.js has the most configurable stuff. You’ll need to use your Twilio credentials and phone number again here.

server.js requires a couple of modules to be installed. Run these three commands to install the express, body-parser, and mongoose node modules.

npm install express
npm install body-parser
npm install mongoose

index.html is the webpage we’re going to access our app with. It uses reminders.js for the AngularJS piece. But first we need to start up our server side NodeJS app. That’s the server.js file. Run this command:

nodejs server.js

If all goes well, the following output will be displayed:

App listening on port 8080

Open a browser and visit {{SERVERIP}}:8080, where {{SERVERIP}} is the IP address or domain name of the server. This should be displayed.

Photo

Let’s try it out. Enter a 10-digit phone number into the box.

Photo

No reminders show up for this phone number. Let’s add a new reminder.

Photo

After clicking on Add, a request is made in the background to our Node.js app, which adds a new reminder object into our MongoDB, returns the object to our AngularJS app, which adds it to the reminders list, and updates the list.

Photo

Pretty exciting! We’ve used JQuery Mobile, AngularJS, Node.js, and MongoDB. But what happened to Twilio?

If you open up another terminal window and run sendreminders.js, the magic will happen.

nodejs sendreminders.js

sendreminders.js checks the queue for messages that should be sent out at the current time. By default, it’s checking every 5000 milliseconds, or 5 seconds. If all goes right, after the specified interval has passed, you’ll see one of two things. If there’s a message set to be sent before “now”, you’ll see the state:

1434143473650
sending post project 1 blog to 6505551234
SM38XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX3

Otherwise, it’s going to state a heartbeat and that no messages have been sent:

1434143473650
no messages to be sent

The number 1434143473650 is the time in milliseconds. When a message is sent, go back to the website, refresh the page, and you’ll see that it has been removed from the list.

That’s all there is for this project. There’s plenty more that can be done with this basic example.

  • Any phone number can be entered. Validate that the number is valid. Twilio has a service for this. Also validate that there is a message.
  • Add the option to text a Twilio number a message like "06/13/2015 14:02 remind me at 2:02pm on the 13th". It should send the message "remind me at 2:02pm on the 13th" at 06/13/2015 14:02.
  • Add the option "in one day, remind me at 2:02pm on the 13th"
  • Add multiple recipients to one reminder.
  • Add a snooze option. Maybe it isn't a good time to complete the task.
  • When a message is sent, remove it automatically from the list in index.html. Right now you have to refresh the page or click on and click out of the phone number field for the list to refresh.

Source Code

You can find the repo on GitHub.

Post mortem

I didn’t have enough time to really dive deep into how MongoDB or AngularJS work. This will likely take a couple more projects to get the hang of it. But MongoDB seems to be pretty neat in storing JSON. And AngularJS is pretty sweet with data binding and moving the data around the app.

I also need to figure out form validation to prevent invalid data from being used.

15 Projects in 30 Days Challenge

This blog post is part of my 15 projects in 30 days challenge. I’m hacking together 15 projects with different APIs, services, and technologies that I’ve had little to no exposure to. If my code isn’t completely efficient or accurate, please understand it isn’t meant to be complete and bulletproof. When something is left out, I try to mention it. Reach out to me and kindly teach me if I go towards the dark side. ;-)

This challenge serves a couple of purposes. First, I’ve always enjoyed hacking new things together and using APIs. And I haven’t had the chance (more like a reason) to dive in head first with things like AngularJS, NodeJS, and MongoDB. This project demonstrated AngularJS, Node.js, MongoDB, Twilio, and JQuery Mobile. That’s a handful in two days, plus a blog post.