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 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 and Padrino, just to name a few. However, none offered the small footprint and modularity that Sinatra 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:
- give a brief overview of Slash Commands and Actions, as well as the concept behind interactive messages;
- guide you through the creation and configuration of a Slack application;
- build a “Surf forecasting” Sinatra application for receiving and sending Slack messages;
- show how to deploy an app using Heroku service or embed it in a Rails application.
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.
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 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.
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.
Navigate to https://api.slack.com/apps and press the “Create New App” button. Add a name and assign it to a workspace of your choice.
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.
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.
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.
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.
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 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 for that.
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). All examples shown are using this gem.
The app will have two types of interactions:
- 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.
- 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.
- Ruby environment correctly configured;
- code from repo — Slash Command Article;
- a working Slack App with a configured /surf command and Interactive Components activated.
- ngrok tool to tunnel all Slack requests to your local dev environment.
- Mapbox account.
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:
Before starting the application, set the environment variables
- MAPBOX_API_TOKEN — create one in the Mapbox’s account page;
- 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 gem.
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.
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;
- 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.
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.
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.
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.
- To send messages with images, you just have to set the property image_url.
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.
There are a couple of things you need to do first:
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.
- 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.
- Inherits from an official Ruby image;
- installs necessary dependencies missing from the image;
- copies the app files to the working directory.
We will be using the Github-Heroku 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 or Container Registry methods.
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:
- or by using Rails routes:
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.
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. It’s still not production ready and there are a lot of interactive component helpers missing.
I work at Runtime Revolution as a Full-stack developer currently focusing on Ruby. My background is .Net and C#, with incursions into Angular and React.
Self care is a hot topic these days, and I’m not just talking about face masks. There is a growing movement that underscores the importance of taking time to take care of yourself (in addition to all the other things that you already take time for). You can prevent problems down the road by taking proactive steps to ensure your health and happiness.