Building a Slack App using Ruby and Sinatra
Tags: sinatra, slack, heroku, ruby
Published: 2019-03-11 14:53:24

---
title: Building a Slack App using Ruby and Sinatra
published: true
tags: sinatra,slack,heroku,ruby
canonical_url: https://revs.runtime-revolution.com/building-a-slack-app-using-ruby-and-sinatra-48c56ad1f051
---

![](https://cdn-images-1.medium.com/max/1024/0*10lZcKeJOYAjJbW9)<figcaption>Photo by <a href="https://unsplash.com/@morrec?utm_source=medium&amp;utm_medium=referral">Morre Christophe</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption>

My team at Runtime Revolution uses an in-house app for team management and other day-to-day chores. Recently, I was asked to integrate [_Slack’s Slash Commands_](https://api.slack.com/slash-commands) into it.

Being new to Ruby I accepted the challenge with added motivation and drive. This was a good side project to improve my Ruby skills.

From the start, I wanted to create a small standalone app that could be easily integrated with our existing Rails application. There are a couple of web frameworks out there. Some offer Rails-like features, [_Hanami_](http://hanamirb.org/) _and_ [_Padrino_](http://padrinorb.com/), just to name a few. However, none offered the small footprint and modularity that [_Sinatra_](http://sinatrarb.com) implies. So I went with Sinatra.

Providing a simple yet powerful DSL, Sinatra enables the creation of web applications with ease. Using community created modules we are able to quickly build a fully-fledged API server with authentication, logging, etc. or a small HTTP module for other Ruby applications or services.

In this article, I will:

1. give a brief overview of _Slash Commands_ and _Actions_, as well as the concept behind interactive messages;
2. guide you through the creation and configuration of a Slack application;
3. build a “Surf forecasting” Sinatra application for receiving and sending Slack messages;
4. show how to deploy an app using Heroku service or embed it in a Rails application.

### 1. Slash Commands and Actions

Slack apps can do more than just sending simple text messages. Using the _Slash Commands_ and _Interactive Components_ (buttons, menus, date pickers, popups, etc.), apps are able to effectively create more engaging experiences.

The diagram below provides a basic outline of the typical flow using interactive messages. Note that the interactions can be triggered by a variety of sources found within the API provided by Slack.

![](https://cdn-images-1.medium.com/max/512/1*7sqqRAtDN49QUDkuMlhQuA.png)<figcaption>Slack interactions overview. Taken from <a href="https://api.slack.com/messaging/interactivity">Slack docs</a>.</figcaption>

In the context of this article, the origin will be a user action triggered by a command_._ The app will then send an enriched message containing buttons, which allow the user to select one of several outcomes presented.

Slack API documentation can be somewhat confusing when referring to actions because almost everything is an action. However, when it comes to configuring a Slack app, as we will see further along, there is a clear separation between _Slash Commands_ and _Interactive Components._

- **What are _Slash commands?_**

_Slash commands,_ as the name states, are commands that the user can execute by writing with a leading / followed by the name of the command. A known example of this is the /remind.

Slack apps can register new Slash Commands, which will instruct Slack to send an HTTP POST request to the receiving server. The app server will act accordingly.

> **Note:** The string _/\<name\>_ is what identifies the command and all words that follow will be the command parameters.

- **What are _Actions?_**

Actions are events triggered when the user selects [a menu action](https://api.slack.com/actions) or clicks any interactive element on a message (buttons, selects, date pickers, etc.). Slack apps can register up to 5 actions in the _Interactive Components_ page or have unlimited actions if they use interactive elements on messages.

Every action is identified by its respective _callback\_id,_ and every time the user triggers one a different HTTP POST will be sent to the receiving server.

### 2. Creating and configuring a Slack Application

There are a couple of things to consider when creating a Slack application for sending and receiving interactive messages. We will have to set specific auth permissions, activate the _Interactive Components_ feature, and configure _Slash Commands._

#### 2.1. Create the app

Navigate to [https://api.slack.com/apps](https://api.slack.com/apps) and press the “_Create New App”_ button. Add a name and assign it to a workspace of your choice.

#### 2.2. Configuring Slash Commands

Having created the app, go to _Slash Commands_ page and press “_Create New Command_”. Fill the form. Set the _Request URL_ with a dummy URL, we’ll come back to this page later.

![](https://cdn-images-1.medium.com/max/983/1*QioZF-Wdui3SVT8-9xgtvg.png)<figcaption>Command form</figcaption>

#### 2.3. Activating Interactive Components

Go to the _Interactive Components_ page and switch “on” the feature. Again, set the _Request URL_ with a dummy URL. This will be updated later, with the actual endpoint. Hit “Save changes” on the bottom.

![](https://cdn-images-1.medium.com/max/998/1*qSx3UUdWaXGmVDpEq4Kauw.png)<figcaption>Interactive components configuration</figcaption>

#### 2.4. Set permissions

Going to _OAuth & Permissions_ page_,_ in the Scopes section, search for “**Add slash commands and add actions to messages (and view related content)**” and save.

![](https://cdn-images-1.medium.com/max/705/1*nlQLIqqxS86GXp1KlhL_1A.png)<figcaption>App permissions</figcaption>

#### 2.5. Installing

After following the previous steps, we are ready to install the app in the selected workspace. Go to the _Install App_ page and trigger the installation.

### 3. Building the app

We are going to build an application that will give us the current surf forecast for a given place in the world. We will use [Windguru’s website](https://www.windguru.cz/1060) to compile the necessary information. For added fun, let’s also include the location map of the spot, so it’s easier to interpret the information given by the variables. We will use [Mapbox’s static maps API](https://docs.mapbox.com/api/maps/#static) for that.

> **Note** :

> During the creation of this article and while coding the example app, I identified some common behaviours for proccessing and sending Slack messages. So, I decided to create a simple Sinatra module and provide it as a gem ([sinatra-slack](https://github.com/nunonamorado/sinatra-slack)). All examples shown are using this gem.

#### 3.1. Interactions

The app will have two types of interactions:

1. the user uses the command /surf today carcavelos for example, to retrieve the forecast information. If the provided variable, _spot\_name_, is enough to pinpoint the desired surf spot, the forecast is returned to the user.
2. If during the first interaction, the search result is ambiguous, the app will send in return a message with multiple buttons one per each surf spot result, up to a limit of 5. This will allow the user to continue the flow.

![](https://cdn-images-1.medium.com/max/835/1*ANzly1NRuP5hOJxYHEvkhg.gif)<figcaption>Interaction 1</figcaption>

![](https://cdn-images-1.medium.com/max/835/1*5azSmb5HsTcXeaMjF3d25A.gif)<figcaption>Interaction 2</figcaption>

#### 3.2. Requirements

- Ruby environment correctly configured;
- code from repo — [Slash Command Article](https://github.com/nunonamorado/slash_command_article);
- a working Slack App with a configured /surf command and Interactive Components activated.
- [ngrok tool](https://ngrok.com/download) to tunnel all Slack requests to your local dev environment.
- [Mapbox account](https://www.mapbox.com/).

#### 3.3. Local development

Slack needs to send requests to valid HTTP endpoints, so we will have to set up some HTTP forwarding using ngrok.

Download the tool from their website, create a free account. When logged in, start the forwarding requests by running,

```
./ngrok http 3000
```

this will start the server and you are now able to receive requests on your dev machine.

Go back to Slack App’s configurations page, in the /surf command configuration paste the ngrok endpoint to the _Request URL_ input and do the same for the _Interactive Components_ section.

The final URLs should look something like this:

- https://\<ngrok-domain\>/slack/commands
- https://\<ngrok-domain\>/slack/actions

#### 3.4. Starting the app

Before starting the application, set the environment variables

- MAPBOX\_API\_TOKEN — create one in the [Mapbox’s account page](https://account.mapbox.com/);
- SLACK\_SIGNING\_SECRET — in the _Basic Information_ page, copy the secret from the _Credentials_ section.

either using the .env file or the command line and start the app by running,

```
bundle exec rackup
```

> **Note:** To use .env, install [dotenv](https://github.com/bkeepers/dotenv) gem.

#### 3.5. Project overview

The S_urforecaster_ app consists of the following files:

- app.rb — your main app file. This is where the Sinatra application will be configured;
- config.ru— this is the Rack configuration file;
- lib/surforecaster.rb — helper class to fetch data from Windguru’s API.

**app.rb**

The code below contains comments for some relevant parts. However, a couple of things to notice:

- The class must inherit from Sinatra::Base ;
- To use the Slack app specific DSL, register the Sinatra::Slack module;
- All requests from Slack need to be verified. By setting the :slack\_secret option, the module will verify each request sent to both commands\_endpoint and actions\_endpoint. For more information about the Slack verification process, [see their docs](https://api.slack.com/docs/verifying-requests-from-slack);
- The quick\_reply parameter in both command and action definitions lets you set the text that the server will send back to Slack, as soon as it receives the command or action request. This is not necessary, but it’s a good practice to let the user know that their request was correctly received and is being processed.
- Under the hood, the module is using Sinatra::Async to process the requests asynchronously. Slack requires that all command and action requests be answered within a 3-second interval, so we need a mechanism to process the request in the background for longer tasks. We will then have to respond with HTTP Post, to the response\_url sent in the original request.
- Command and Action signature definitions use the Mustermann string matching definitions. Looking at the _command_ signature definition /surf \*granularity :spot\_name, the **\*** allows catching the first word in a non-greedy way, leaving the rest of the words for the _spot\_name_ variable. To know more about it, [go here](https://github.com/sinatra/mustermann/blob/master/mustermann/README.md#-sinatra-pattern).

{% gist https://gist.github.com/nunonamorado/f9aed7b51172da8cbab579e08028d6c6.js %}

**config.ru**

We will use the rackup command to start the Sinatra application. The config.ru file contains all the necessary options for the app to run. Notice the topmost comment, this is where you can set all command-line options which rackupwill pick up.

{% gist https://gist.github.com/nunonamorado/81c998ecec848263195967f57acc2550.js %}

**lib/surforecaster.rb**

This is just a basic ruby class for fetching forecast information. It contains 3 methods that will be used in different stages of the command and action flows.

{% gist https://gist.github.com/nunonamorado/f36fc101668707fd1997930db155e384.js %}

#### 3.6. Adding interactivity to messages

As described above, Slack responses can be simple text messages or more complex, with rich text, images and other actionable elements like buttons and menus.

For _Surforecaster_ app, we will need to send both messages with images and buttons.

- To send messages with interactive components, two message properties are required — _callback\_id_ and _fallback_. In the example below, we are using a helper method to build a slack message and pass along the _callback\_id_ value. If you look closely, we are setting the same name as the action signature defined in the app.rb, using the string interpolation to set the _callback\_id_ according to the current command _granularity_ (that was sent by the command)_._ This is a nice way of passing around values between Slack and the app, without having to cache it somewhere else.

{% gist https://gist.github.com/nunonamorado/d06589718c480a86c8fae31e7afd4311.js %}

- To send messages with images, you just have to set the property _image\_url_.

{% gist https://gist.github.com/nunonamorado/c354a2a46b771999b613b5472cbd2dd9.js %}

### 4. Deploying

The final step of this exercise is deploying the app we just built. As previously stated, Sinatra apps can be deployed as a standalone app or embeded in a Rails application.

#### 4.1. Standalone mode

We will be deploying _Surforecaster_ using [Heroku](https://www.heroku.com) and [Docker](https://www.docker.com/).

There are a couple of things you need to do first:

- [create an account on Heroku](https://signup.heroku.com) or use an existing one;
- [download and install Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli#download-and-install).

Create a new app in Heroku using,

```
heroku create \<app-name\>
```

Then, we need to set your app stack to container. Run the command,

```
heroku stack:set container -a \<app-name\>
```

Set the values for the two environment variables (SLACK\_SIGNING\_SECRET and MAPBOX\_API\_TOKEN) either in the app settings page, c_onfig vars_ section or using Heroku CLI with,

```
heroku config:set SLACK\_SIGNING\_SECRET -a \<app-name\>
```

Now, let’s look at these 2 files:

- heroku.yml — this is the manifest file with the definition of the Heroku app;
- Dockerfile — contains the instructions for building the app image that will be deployed.

**heroku.yml**

- build.docker.web tells Heroku to build the Dockerfile at the root of the project;
- run.web command is executed when launching the container. Heroku doesn’t let us define a static port number, so we have to set it through an environment variable, $PORT.

{% gist https://gist.github.com/nunonamorado/e4f89a00291564c0f734e084244ee966.js %}

**Dockerfile**

- Inherits from an official Ruby image;
- installs necessary dependencies missing from the image;
- copies the app files to the working directory.

{% gist https://gist.github.com/nunonamorado/8ec90babdbf5f022a442a38c221765fc.js %}

**Integration**

We will be using the [Github-Heroku integration](https://devcenter.heroku.com/articles/github-integration) for automatic deployments, so that every time a code change is pushed, Heroku will build and deploy the application. However, you could also choose to deploy using [Heroku Git](https://devcenter.heroku.com/articles/git) or [Container Registry](https://devcenter.heroku.com/articles/container-registry-and-runtime) methods.

#### 4.2. Embedding in a Rails App

Another way of deploying this app is to embed it in an existing Rails app. You can do this in two ways:

- Changing the config.ru file to something like this:

{% gist https://gist.github.com/nunonamorado/2ea06c70ffcf6e89d59716266364bb2e.js %}

- or by using Rails routes:

{% gist https://gist.github.com/nunonamorado/c3eba64d29d1adb96dd16252ee0678b7.js %}

### Last step

After the deployment is complete, copy&paste the new URL to the Slack app configuration pages and that’s it! Go to the Slack workplace and start “commanding” stuff.

### Future work

By creating Slack Apps with Interactive Components and Slash Commands we can accomplish really useful integrations that can simplify any company’s processes.

Keep in mind that we’ve barely scratched the surface in terms of interactive elements, we can still experiment further with select menus, date pickers, popups, confirmation messages, etc.

During the making of this article, I created a Sinatra module accessible through the [“sinatra-slack” gem](https://rubygems.org/gems/sinatra-slack). It’s still not production ready and there are a lot of interactive component helpers missing.

_I work at_ [_Runtime Revolution_](http://www.runtime-revolution.com/) _as a Full-stack developer currently focusing on Ruby. My background is .Net and C#, with incursions into Angular and React._

* * *