Serverless Slack Bot

Using AWS Lambda

Introduction

Lobbipad.com is a platform for welcoming your visitors at the reception. It consists out of an iPad in the lobby of your company and a backend to manage and report all the visitors.

1 of it’s core features is notifying hosts (or employees) that their visitor arrived in the lobby. For now we are notifying hosts using E-mail or SMS. The sms is small message: “John Doe has arrived in the lobby”. For email we use a longer message with more details.

This all works fine, but a lot of hosts are on Slack during working-hours. It would be great if we could notify them using Slack.

We could easily integrate this part in the Node.js stack we currently have at Lobbipad. But we went for a more scalable path: AWS Lambda.

AWS Lambda

Start of by creating an AWS Account: aws.amazon.com.
After this check out the Lambda service:

The Lambda First Time User Experience

It’s perfectly possible to create a Lambda function using the web-portal:

In this screen you can choose from the templates

When choosing for Blank function you can start to work on your own function. The editing happens in browser.

Congrats you made your very first function!

Serverless Framework

But this is not easy to maintain. As developers we want to

  • Create locally
  • Test locally
  • use an SCM (e.g. GIT)
  • deploy from our local machine
  • Test remotely

That’s where Serverless.com kicks in. Serverless is a framework written in Node.JS which makes it easy to integrate with Lambda (but also with Microsoft Azure or IBM OpenWhisk). It provides a boilerplate to get started and also handles the heavy-lifting of configuration on each platform.

Starting is as easy as

There is 1 pitfall in this: AWS credentials. If you are not familiar with AWS IAM then you can follow their Video Guide.

After setting up this we can develop and deploy our first function.

Configure your Slackbot

Before diving into the function we need some an App on Slack. This can be easily done by going to api.slack.com. Fill in a name for you notification bot and add it to a Team.

Example of the form to create a new Slack App

Now we can start for real. Open the previously generated directory in your favourite editor (Mine for Javascript is Visual Studio Code).

You’ll find 2 files:

  • Handler.js
  • serverless.yml

Serverless.yml is the configuration file. (only the interesting parts)

service: superduperbot # NOTE: update this with your service name
provider:
  name: aws
  runtime: nodejs4.3
  region: eu-central-1
functions:
  hello:
    handler: handler.hello

As you can see we defined the name (superduperbot), the provider with it’s location and stack. Next to these we defined the functions. These are the functions that should be available.

Handler.js is the real code. This is just Node.JS. So you can use this as a normal Node.JS project. npm is available, node-modules, etc.

So in terminal do “npm init” and fill in the information needed: (Pay attention to App.js)

➜  serverlessdemo npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help json` for definitive documentation on these fields
and exactly what they do.
Use `npm install <pkg> --save` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
name: (serverlessdemo) superduperbot
version: (1.0.0)
description: The superduperbot
entry point: (handler.js) app.js
test command:
git repository:
keywords:
author: Matthias Nys (B-NYS)
license: (ISC)
About to write to serverlessdemo/package.json:
{
"name": "superduperbot",
"version": "1.0.0",
"description": "The superduperbot",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Matthias Nys (B-NYS)",
"license": "ISC"
}
Is this ok? (yes)

After this your project can use npm to install dependencies.

Go ahead and create a function called sendMessage in handler.js

module.exports.sendMessage = (event, context, callback) => {
const response = {
    statusCode: 200,
    body: JSON.stringify({
      message: "Send a message",
input: event
    })
  }
  callback(null, response)
}

After doing this register this function in your serverless.yml

functions:
  sendMessage:
    handler: handler.sendMessage
    events:
      - http: POST sendMessage

This will configure the API Gateway (AWS Service) to expose the handler.sendMessage as a POST HTTP endpoint.

Now we can deploy this echo to AWS. Navigate in terminal to the root of your project and run the following command:

serverless deploy

You’ll see something like this:

Serverless: Packaging service...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading service .zip file to S3 (4.97 MB)...
Serverless: Updating Stack...
Serverless: Checking Stack update progress.................
Serverless: Stack update finished...
Serverless: Removing old service versions...
Service Information
service: superduperbot
stage: dev
region: eu-central-1
api keys:
None
endpoints:
POST - https://x8ufk5j81l.execute-api.eu-central-1.amazonaws.com/dev/sendMessage
functions:
superduperbot-dev-sendMessage

The most important thing is we can test our endpoint now at the URL posted in the response. Use this cURL command or your favourite API testing tool (Postman or Paw):

curl -X POST -H "Content-Type: application/json" -H "Cache-Control: no-cache" -d '{
"visitor": {
"first_name":"Showpad",
"last_name":"Test"
},
"host": {
"first_name": "Matthias",
"last_name": "Nys",
"email":"dummy@b-nys.com"
}
}' "https://x8ufk5j81l.execute-api.eu-central-1.amazonaws.com/dev/sendMessage"

Replace the URL with your own endpoint.

In Lobbipad we have webhooks. These are available for 3rd parties to integrate with our platform. We fire a webhook on several events, 1 of them is the visit event. So every visit that is registered we call the webook.

That’s also what you can see in the body of the webhook. This is simplified for this example. More information about our webhooks can be found at https://github.com/theoffix/lobbipad-api-docs.

Post Message from Lambda to Slack

There are 3 steps to send a Personal Message to the Host:

  1. Get list of Users (+ email)
  2. Filter the list to get the userId of the Host on Slack
  3. Send a personal message to the host using the Bot

Getting the list of users

https://slack.com/api/users.list?token=[SLACKTOKEN]

If you have the correct permissions this should return the list of users in your Slack.

Filter the list of users

Using a simple matching algorithm:

user.profile.email === host.email || (user.profile.first_name === host.first_name && user.profile.last_name === host.last_name)

Sending the personal Message

The Slack API provides an easy API call for this. Do a GET call:

https://slack.com/api/chat.postMessage?token=[TOKEN]&channel=[hostUserId]&text=[URLEncodedText]&username=[BotUserName]&as_user=false

The final code can be found on github: https://github.com/matthiasnys/SuperDuperBot

Bringing it to production

It’s obvious that this code is not production ready. It only supports 1 Slack channel, credentials aren’t handled properly.

To make it production ready we added some tweaks.

Authorization Header

All requests to the API Gateway must have a Authorization Header that contains a JWT token we can verify. That way we can verify that only known sources can start sending.

Slack Button

How a Slack Button looks in production environment

The buttons provides a one click gateway to install your Slackbot into a Slack instance. It handles the heavy lifting of the oAuth procedure. It returns all the information needed to start your slackbot.

Slack API Tokens

We store the Slack API tokens on our own server. They are accessible for the Lambda instance using a authorized API call. That way we can easily deploy without worrying too much about the secure storage.

Cloudwatch

Because Lambda is on AWS you can easily monitor your functions using CloudWatch. They give you a nice dashboard with error, logging,.. everything you need to build greater experiences.

Conclusion

It was a nice experience to work with AWS Lambda in combination with Serverless framework. I can agree that you can easily build and host these functions in your main codebase. But it’s also nice to have a lightweight service to send stuff to Slack. You don’t have to maintain it (update, monitor,…) and you only have to pay per execution.

For us this is the path we will take to build more (and more powerful) integrations for our products.

Big Thanks to Anthony and Jeroen for assisting on this one!

More information about our awesome products at TheOffix.com