A Slack App Step-by-Step: A Real-Life Example

A Slack App Step-by-Step: A Real-Life Example image

In the previous post, I explained a very basic approach to creating a slack integration without the need for deploying a server platform and with minimal coding. Now, it's time to dive into a more advanced usage example.

Today's Goals

I will cover creating Slack App and a server handling backend logic of the slash command requests. I will show you how to create a local development environment, step-by-step configuration, installation, and authentication of a Slack App.
Then, I will show how to deploy a finished service and how to distribute a Slack App to other teams. This is dedicated to developers interested in the complete spectrum of Slack App development. To understand the code, you will need to know Node.js, Express.js, and the basics of HTTP, API, and server operations.

Creating a New Slack Team

When it comes to Slack App development, I encourage you to create your own Slack team. That way, you will be sure not to either spam, mess up channels' history, or distract other members of your team. You will also have admin permissions, which might be useful depending on what kind of functionalities you expect from your future app. Creating a new team in Slack is easy and free.

About the Example Project

To illustrate the various Slack App and Slack API's capabilities, I will create a real-life example — a tool used to share new job openings and stimulate referrals. We will call it the Bounty Hunter.

When designing an application, it's beneficial to define its functionalities up-front (if possible). So what will the Bounty Hunter App do for us, and why is it even considered a Slack App? Wouldn't posting job openings by hand be an easier way to inform people about job openings? Yes and no. It depends on the use case.

Imagine you have a huge company which hires 100+ people. The company hires new employees only by referrals. It also offers a bounty of $100 for each hired employee to the referrer.

There is a big number of job openings at the same time, and at some point, it might become unmanageable. Perhaps recruiters might even want to integrate it with an external tool that manages all job openings, in which case we need to ensure communication between them. There also might be a need for easy access to a full list of job openings, a better UI to respond to a job opening, or one of the multitudes of other problems.

However, for now, let's focus on defining the most basic functionality, keeping in mind our app might grow to become all of the above.
We will define the posting of job openings to a public channel. My requirement for this is the following:

"I need to be able to provide details of job opening inside Slack, and I expect it to be posted directly to the public channel which is considered the best place for such announcements.
Once this job opening is posted, it should automatically be pinned to that channel."

At this point, we are still not making the recruiter's life any easier so let's put the cherry on the top:

Each time a new user is added to the Slack Team, the Bounty Hunter App should pick that up and send me a nice message asking if that user was a referral. In such a case, two things should happen:

  • the job opening should be automatically removed from the public channel

  • the person who referred the new hire should receive their $100 (or at least a notification about it)

Also, I would like all job openings to be automatically reposted to the channel each week (without the already filled ones).

Creating a New Slack App

Slack's API page is your go-to place for API specs and Apps management. There, you can discover the new possibilities each Slack API offers (there is more than one), test methods, and learn about general guidelines.

To create new Slack App for your can click the "Start building" button, which will lead you to the "Create Slack App" form. Here, you can specify a name and choose a "Development Slack Workspace". To do so, you will need to be logged in to you Slack Team inside your web browser. Once you are logged in, you can select your team and press the green button.

Create a Slack App dialog

You will be redirected to a Slack App configuration page where you can adjust it to your needs. You will see lots of information there, as this is a potent tool, but we will focus only on the essentials required for our project.

Cosmetics

In the first view, when you scroll down, you will see the section "Display Information". This is the place where you can add a description, app icon, play with the background color, and change the app name.

Features and Functionality

Let's focus on what our app will need to do. Out of the box, any new Slack App has no functionality attached. It's an empty vessel, which you need to configure to do anything. To know what I need to configure I need to understand what business logic my app will provide. We already defined the scope of functionalities we expect from our Bounty Hunter app, but how will they actually work in a Slack environment?

Our first functionality was to create a new job opening. There are several ways to achieve that but, because of the simplicity of usage and configuration, the "slash command" seems to be the best option. The Slack command is probably the least complicated service in Slack. It is accessible globally right in your chat input area. Each command can be featured in the special popup which narrows down the possible commands as you type. Each command has one purpose: send a request, with or without a body, to a specified HTTPS endpoint.
I will create a slash command that will be responsible for creating new job openings.

Create the Slash Command

Let's get back to our Slack App configuration page's "Add features and functionality" section and click "Slash Commands". Click "Create New Command" and get familiar with the form.

Slash command configuration

First I need to specify the command name under "Command". This is the name you will provide after "/" to run the command. I will name it "/add-job-opening".
Next, I need to specify an endpoint for the "Request URL", which at this point I don't have. But what's an endpoint in that case and why do I need to provide that?

As I said before, a slash command sends a request to an external web service (usually an API). Slack does not provide any way to resolve that request and we need to provide it on our own. To do so, we need to provision a web server. Keep the form open for now until we get back to it.

Local Development Environment

I will be writing the server part in Node.js and the popular Express.js. Start by initiating the NPM project and GIT repository.

My preference is ES6 syntax, ESlint, and Yarn so this is the command I run to setup bare minimum for our project:

> yarn add express body-parser

and for the dev environment:

> yarn add --dev babel-cli babel-core babel-preset-es2015 babel-preset-stage-0 eslint nodemon rimraf

Then I created routing to handle all /api endpoint requests and created an endpoint for slash commands /api/commands.
A separate controller will handle all requests coming to that route.

https://github.com/jacekelgda/slack-app-dev-post/commit/11365fa679300c8ef42fe0e590ebbf77b5dcd49d

I can now run the development version of my app with the command yarn dev and visit http://localhost/api/commands to see dummy response text from the controller. Currently, Express will handle all types of requests, so that we can see results in the web browser but in the Slack App configuration, we can specify the HTTP method type.

There is still one step to open up our server to Slack since, currently, it's not accessible from the outside. There is a simple way to do that: Ngrok. It will open a tunnel to a local port and generate a URI for it.
Install Ngrok on your machine and copy the HTTPS endpoint:

>ngrok http 3000

Ngrok working

Copy the https URI and paste it into the slash command form thus adding a route to our commands endpoint: {ngrok url}/api/commands.
All should look like this.

Configuring the slash command

Save the new command.

Then you need to Install App to your workspace. So far, you only created it and added a command, but it has never been installed until now. You can find the installation page in Settings > Install App. Click the green button.

You will see the authorization notice. Slack is making sure you understand what parts of your Slack Team will be exposed to the app. So far, we only configured the slash command, so this will be the only thing our app has access to. Technically speaking, we are adding a permission scope to our Slack App called commands.

Setting scopes

OAuth & Permissions

Let's explain permission scopes. In order to perform specific actions, a Slack App needs to have access to API methods. Each API method has a defined scope required for it.
If a Slack App would like to add its own command to the workspace, it needs to have that scope added. Scopes are bound to Auth Tokens, which we will use later. Each time you change permission scopes the Slack App uses, it needs to be reinstalled. After authorizing the Slack App to your workspace, you should get an email about the installation and see a newly generated OAuth Token.

Running the Slash Command

Open your Slack application and try running the command that was added together with our Bounty Hunter App.

Running the slash command

Just like this, we are showing the response from our local server. There are several things to note here. Did you notice the "Only visible to you" message? This means that this is a special type of a message — "Ephemeral". This type of a message is only visible to you and also will not persist in your Slack Client. It will be gone after a refresh and will not be visible on other devices. It can only be sent to active users.
It's a great tool for development, as even in crowded, public channels, those messages will remain visible to only you.

Receiving an ephemeral message

Implementing Logic

We have tested a complete loop of communication between the Slack platform and our own web service. Now, it's time to add functionalities we defined for this app.

We can now tell when someone runs a command.
But what about providing the details of the job opening? Where can I specify that?

Each command takes exactly one parameter: "Text".
It is everything you write after the command name, separated by a space.
Let's try adding a job description and log what the request looks like:

Passing a job description with the command

Request log on the server side

You can see that each slash command request body provides a set of useful information. We can see the name of the actual command that was used, the person who runs it, all the details about the Slack team, user, channel, and a text field. The text field is what the user has attached to the command. That's what we can use to define the job opening description. However, we can already see that there are limitations to slash commands. Since there is only one parameter I can add to each command, it will be difficult to specify additional data that might be important to a job opening, like a title.

Of course, we could parse the text and look for a specific format that the recruiter would have to use — and that's definitely an option — but later, I will show you how Slack came up with a better tool for this. For now, let's stick to the slash command text and pass all the details in there.

Posting a Chat Message

Getting back to our app's logic, let's think about the second part of this request. Yes, we replied back to the user that started the command with "Thanks", but where is the message posted to the public channel? We still need to implement that.
This is the first time we can send a request to the Slack Web API. Our app should make a request to the Slack Web API, which will send a message to the channel we specify. The message will share the details of a new job opening.

The Slack Web API method we will use in this case is "chat.postMessage" (https://api.slack.com/methods/chat.postMessage).
In docs, you can see it requires three parameters. It also tells you which permission scopes are required to access it.

If you switch from the "Documentation" tab to the "Tester" tab, you can actually test each method before you use it in your app. That way, you can decide up-front if this is what you are looking for. Let's try it.

Your token should already be selected (make sure you select the right workspace, in case you are logged in to several at the time). In case there is no token yet, you will need to use the link below to generate it. Next, you need to specify a "channel" or, to be more specific, a channel ID. You can use the shortcut for the channel "general". If you would like to get the ID of a different channel, you need to get the full list of channels in your team. The method "channels.list" is perfect for that (https://api.slack.com/methods/channels.list/test). This method actually doesn't need any additional parameters, so it's quite quick. I look for the name of the channel I want to use and copy the channel ID. I will pick the "random" channel for this test since it is created by default in every team.
Press the "Test method" button and see what was posted to the slack channel. This is the method we will be using to post messages to the channel.

Method tester screen

Let's get back to our app and create a service that will be responsible for communication with the Slack Web API.

I need to add a new package that will help create requests to the Slack Web API — "slack"

> yarn add slack

It's a very easy-to-handle client that wraps all requests in Javascript Promises.

Most of the requests to the Slack Web API requires the authentication token to be passed along with other required parameters. I can copy the OAuth Access Token from the Slack App configuration page in the "Features > OAuth & Permissions" section.

To complete the configuration, I need to add a new permission scope in the Scopes section. It is mentioned in the documentation (https://api.slack.com/methods/chat.postMessage) that this method requires the chat:write scope (https://api.slack.com/scopes/chat:write).
There are two types of this scope, and I choose chat:write:bot as this will show all the messages as sent from the Bounty Hunter app. Changing apps' permission scopes requires a re-install in the workspace and another authorization.

I don't want to keep my apps' sensitive credentials in public domain, so I need to make them part of the environmental variables. For the local development environment, I want to have easy access to them, so I create a config file called .env, which is added to the .gitignore file (a configuration file which tells GIT which files in the current directory not to include in version control). I hold all sensitive credentials right there on my local machine. There is another package I need to add, called "dotenv", which loads all credentials to the process.env variable at runtime.

> yarn add --dev dotenv

I include the .env.TEMPLATE file inside my repository to state which config variables are required to run my app. At this point, I would like to include the OAuth Slack App token and the Slack Channel id in there.

Code: https://github.com/jacekelgda/slack-app-dev-post/commit/88e944b08b71cd6f4874eafd6211e66a3f2722cf

Just to make our app messages look cool, I will set an app icon. The requirement for an icon image is: "squares between 512px by 512px and 2000px by 2000px". It can be added in the "Basic Information > Display Information" section of the configuration page.

Let's test our app's command:

/add-job-opening We have a new job opening: Node.js Developer. Our client is looking for a senior role. Bounty for referral is $100.

Sending a full job description

Validation

There is a security vulnerability in the app we created so far, and that's an open API endpoint. Right now, anyone could create a request to the /api/command endpoint and could have an easy way to spam the slack channel. Slack provides a "Verification Token" for each application, which is a parameter passed with each request to any specified endpoint. I should implement the verification token check for each request. I will add a new config variable "verification_token" and copy the token value from the "Basic Information > App Credentials" section. Next, I will create a token verification middleware for the API route.
I will also change the /commands route to only accept the POST method as this is the method Slack will use.

Code: https://github.com/jacekelgda/slack-app-dev-post/commit/eb95a428566f731be530fd2d307e4daacc9188b4

Summary

This part is finished. We have covered the basics of Slack App configuration and built a bare minimum API server to handle a slash command's request. We also used the Slack Web API to send messages to public channels. The next blog post will explain more advanced functionalities: pinning a message, formatting a message with attachments, and providing interactive components.

KEEP MOVING FORWARD

Jacek Ławniczak / code