Scheduling events in the future with Meteor (Email example)

Motivation

Having just implemented a server-side task scheduler with Meteor that allows the user to schedule a given task to take place exactly once in the future, and then answered a question on the same subject on Stack Overflow, I thought I’d share my method here.

Synced-Cron

As the number of Meteor packages on Atmosphere increases exponentially, one of the best guarantees of quality is the percolate namespace. They’ve released the excellent synced-cron package, which is far more powerful than the requirements of this use case, but it still works better than any other package I’ve come across.

$ meteor add percolate:synced-cron

Set up your task and a schedule collection

Set up a server-side collection to store your tasks in case of server reboot, and the body of the task you want to complete (in this case, sending an email).

FutureTasks = new Meteor.Collection('future_tasks'); // server-side only

// In this case, "details" should be an object containing a date, plus required e-mail details (recipient, content, etc.)

function sendMail(details) {

	Email.send({
		from: details.from,
	        to: details.to,
        	etc....
	});

}

Add functions to schedule and record your tasks

function addTask(id, details) {

	SyncedCron.add({
		name: id,
		schedule: function(parser) {
			return parser.recur().on(details.date).fullDate();
		},
		job: function() {
			sendMail(details);
			FutureTasks.remove(id);
			SyncedCron.remove(id);
	        	return id;
		}
	});

}

function scheduleMail(details) { 

	if (details.date < new Date()) {
		sendMail(details);
	} else {
		var thisId = FutureTasks.insert(details);
		addTask(thisId, details);		
	}
	return true;

}

Process existing tasks on reboot, and start the Cron

Meteor.startup(function() {

	FutureTasks.find().forEach(function(mail) {
		if (mail.date < new Date()) {
			sendMail(mail)
		} else {
			addTask(mail._id, mail);
		}
	});
	SyncedCron.start();

});

Then, you can just call scheduleMail from the server whenever you want to add a new task to the queue.

Conclusion

Hopefully, the code above is straightforward, as is the path to generalising to other use cases beyond sending email. If there are any questions or suggestions, just let me know.