May 9, 2016 · Practical Automation

Creating my first Slack bot with Heroku

I've always been a firm believer that the best way to learn is by trying (and failing), and last week I had the perfect opportunity to do just that. It began with a slight annoyance, and within a few hours, I'd coded a solution which not only eradicated that annoyance, but also left me with a bagful of useful new skills.

A couple of people have asked me how I did it, so let's go back to the beginning....

The problem

At Compare the Market, just like most offices, we have a large number of meeting rooms, across several floors. The room names are occasionally changed as they're repurposed, and some of them make surprisingly little sense (The Cellar, for example, can be found on the 2nd floor).

Because of this, meetings are always a pain for me. When I get a meeting reminder, I have to locate my master list of meeting rooms (a PDF plan of the office which is buried in My Documents) and work out which room I'm going to.

What I wanted was a way of asking Slack to tell me where I needed to go: I wanted to be able to type /where is <room name> and get directions to that room.

So that's what I built!

The key components

My first port-of-call was Slack's API documentation, which includes an extremely useful guide to creating slash commands. After creating a bare-bones command, I now knew that I could fire a request to an external URL with a known payload. All that I needed was to create the endpoint and script its handling of the request.

I opted for Heroku, for two main reasons: there's a free tier (good enough while I'm evaluating) and they had the perfect blog post which describes (with examples) how to deploy your Slack bots to Heroku.

Scripting

After cloning the Starbot example from the above blog post, and stripping it down to a simple "hello world" response, it was just a simple case of coding my Javascript to respond with the text/image that I wanted. At the moment, it's an inelegant switch statement, but as an MVP, it does exactly what I need:

switch(requestText) {  
      case 'cellar':
      case 'the cellar':
            responseText = "The Cellar is on the second floor, right hand side."
            attachment = secondFloorImage      
            break;
      <etc>

And hey presto, when I ask Slack /where is the cellar:

What I found most rewarding was that I reached this point extremely quickly. It took about an hour to get a simple response to my bot (and that was mostly spent familiarising myself with Heroku), and another hour to flesh out the case statements in the script.

From here, it was just a simple matter of improving the user experience through management of sleeping and logging.

Sleeping

Heroku's free tier has a couple of understandable restrictions. Firstly, after 30 minutes of inactivity, the dyno will go to sleep, and will wake itself when the next request is made. This is problematic for a Slack integration, as a slash command returns a timeout error if there's no response within 3000ms; not a great experience for my Slack users.

Secondly, in order to prevent freeloaders from pinging their sites constantly to bypass sleeping, the Free tier has a requirement that the site must sleep for 6 hours within any 24-hour period.

Because of these two restrictions, and because I'm still evaluating the usefulness of this bot, I'm taking advantage of a service called Kaffeine which pings the site at regular intervals, and also supports a built-in 6hr sleep period. This means that, during working hours, my bot's endpoint is always awake and responsive.

Other similar ping services are available, though (much to the consternation of online communities) they're often seen as an attempt to freeload or bypass the paid tiers. That's not the case here - if the bot proves useful, I'll be upgrading to the $7p/m "Hobby" tier which has 24hr uptime by default. But I don't want to add my credit card details until I'm sure this is going to be any use!

Logging

While I'm trialing the bot, I wanted to get an idea of who's using it and what words they're searching for, so that I can train it to recognise popular search queries. Again, through Heroku (and its add-on interface), this is dead easy!

Firstly, I put a simple log message within my script:

console.log("REQUEST: " + payload.user_name + " (" + payload.channel_name + "): `/where " + payload.text + "`")  

Then, I installed one of Heroku's many log-parsing solutions (I opted for Papertrail as it has a particularly elegant querying interface). By filtering for the above log message format, I was able to easily monitor incoming requests:

Papertrail log

Papertrail also supports a daily email alert, so I get query summaries emailed to me each morning.

Mission accomplished?

Along with my requirements, I had a basic set of acceptance criteria in mind:

  1. Whether my own meeting experience is improved
  2. Whether others are benefitting from the bot
  3. Whether the bot knows the answers to the queries it's asked

I'm certain I've achieved #1, as I've now been able to delete my old PDF! As for #2 and #3, just like any live service, I'll be evaluating these using monitoring / metrics through the Papertrail console.

If it's been as successful as I think it has been, I'll be promoting it to a fully-fledged Heroku project - and I'll certainly be using Heroku again for projects like this in the future!

  • LinkedIn
  • Tumblr
  • Reddit
  • Google+
  • Pinterest
  • Pocket
Comments powered by Disqus