Skip to main content
Logo, a drawing of a warm coffee vgagaleski

Back to all blogs

Build a Slack automation bot in one hour

Published on by Viktor Gagaleski · 10 min read

As SDETs, we’re not just testing software, we’re enabling teams to deliver hight quality software faster. What if your team could trigger builds, get CI/CD updates, or file tickets for failures, all without leaving Slack? In this post, we’ll build a lightweight Slack App in under an hour that acts as a proactive helper bot. It’ll automate boring and repetitive tasks, improve developer experience, and let your team focus on what really matters.

Why do we need bots?

SDETs today are expected to do much more than just write automated tests, we’re enablers of quality at speed. Our mission is to identify friction in the software delivery lifecycle and eliminate it with smart, scalable solutions. One of the most overlooked ways to do this is by building internal tools that developers actually use, and in this blog I will be exploring automation tools with Slack.

Slack has become the command center for engineering teams. Builds fail, PRs get merged, incidents are reported — all in real time, right in the chat. Yet, when something breaks or a test pipeline fails, people still context-switch: open tabs, navigate dashboards, run CLI commands, or chase flaky tests manually. This wastes time and disrupts focus.

Now imagine this instead:

  • If a test suite fails, your bot suggests opening a Jira ticket, and files it in one click.
  • A developer runs /build status to check if their PR is blocked.
  • Your bot replies with the CI status, last run details, and a rerun button.
  • Test reports, flaky test stats, even triage checklists, all delivered where people already work.

As SDETs, we have the skills to make this possible. By building Slack bots that automate common tasks, we’re not only speeding up delivery, we’re making quality more visible, debugging faster, and developer lives easier. This isn’t about replacing tools, it’s about bridging them. Slack becomes the interface, and your bot becomes the glue.

Prerequisites: What you need before we start

Before we jump into building our Slack bot, let’s make sure your environment is ready. You don’t need much, this setup is lightweight and approachable for any SDET or developer with basic scripting experience.

Here’s what you’ll need:

✅ Local Setup

  • Node.js (v18+) – We’ll be using Bolt for JavaScript (TypeScript) Template App, Slack’s official framework.
  • A Slack Workspace – You need admin access to create and install a custom app. You can setup your own Slack workspace for free.
  • Basic JavaScript/TypeScript Knowledge – No need to be a frontend expert, but familiarity helps.

🛠️ Tooling You Can Integrate

  • Jira Cloud for ticket creation
  • GitHub and GitHub Actions

The idea here is to keep your first version simple and fast, then expand with integrations your team will value most. Once you’ve got your tools lined up, we’ll move straight into building the bot.

Getting started: Your first Slack App in 15 minutes

If you’ve never built a Slack bot before, don’t worry, the official guide from Slack is excellent. I recommend cloning the repository linked above - Bolt for JavaScript (TypeScript) Template App. Follow the tutorial to setup the Slack App properly.

Here’s what you need:

  • Signing Secret – Found under your app’s Basic Information page, used to verify requests from Slack.
  • Bot Token (with channels:history, chat:write, commands) – Generated when you install your app to your workspace, it allows your bot to interact with Slack APIs. This token starts with xoxb-
  • App Token (with connections:write scope) – Required if you use the Socket Mode, which is what we will use for local development. This token starts with xapp-
  • Under Event Subscriptions, toggle the switch labeled Enable Events (message.channels, message.groups, message.im, message.mpim)

These secrets are stored in your .env file:

SLACK_CLIENT_ID=YOUR_SLACK_CLIENT_ID
SLACK_CLIENT_SECRET=YOUR_SLACK_CLIENT_SECRET
SLACK_SIGNING_SECRET=YOUR_SLACK_SIGNING_SECRET
SLACK_APP_TOKEN=YOUR_SLACK_APP_TOKEN
SLACK_BOT_TOKEN=YOUR_SLACK_BOT_TOKEN

Once all of the secrets are setup, run npm install and then npm start to start the server. You will see the following message in your terminal: bolt-app ⚡️ Bolt app is running! ⚡️. In order to interact with the bot, you will have to add the App as Integration in some of your channels. And just like that, you’ll have a local bot up and running, ready to respond to commands or events. Send hi message and you should receive back hi, how are you?. Isn’t this cool? You just created your first simple Slack bot automation.

It took me about 15 minutes to go from zero to a working Slack bot that replied to hi in a dev channel — including setup, running it locally, and seeing my first interaction in Slack.

Create Slack command

With your Slack App up and running, it’s time to make it do something useful, something your team would actually use. Let’s extend the bolt-ts-starter-template with a command that developers and SDETs will appreciate right away:

🚀 /jira – Instantly create Jira tickets from Slack

How many times have you seen a test failure or CI issue in Slack, and someone has to open Jira manually to report it? Let’s eliminate that.

We’ll build a /jira report command that will create simple task ticket:

  • title: Short summary
  • description: Full details
/jira report `Failed checkout flow` `Checkout fails for guest users on Firefox`

Step 1: Create slash command in the Slack App configuration

In order for the Slack to be able to recognize the /jira command we will have to create a slash command in the App configuration.

  • Go to https://api.slack.com/apps:
  • Choose your app.
  • Navigate to: Features → Slash Commands
  • Ensure you’ve added /jira as a command.
    • Command: /jira
    • Short Description: Interacts with Jira
    • Usage Hint: Title Description
  • Make sure Socket Mode is enabled so you won’t need to specify a Request URL.

Step 2: Jira setup

You can integrate this with your actual Jira instance via REST API. For this example, I created my own Jira instance and I have setup a small team projects with project named DEV.

In order to connect to Jira, we will need to setup an API key and few other env vars. Navigate to https://id.atlassian.com/manage-profile/security/api-tokens to create API key. Add the following env vars to the .env file:

JIRA_HOST=YOUR_JIRA_HOST
JIRA_USERNAME=YOUR_JIRA_USERNAME
JIRA_API_TOKEN=YOUR_JIRA_TOKEN
JIRA_PROJECT_KEY=QA
JIRA_ISSUE_TYPE=Task

Step 3: Coding and logic

Now that we have the connection details, let’s first install jira-client, date-fns and some types by running:

npm i jira-client
npm i date-fns
npm i --save-dev @types/jira-client

Now comes the fun part — writing the actual logic. We will first create a minimal wrapper around the Jira API. It will provide us with basic code to create ticket, and we can extand it as we want in the future, based on our needs. The generateADFDescription gives us rich-text formatting in Jira (ADF = Atlassian Document Format) which is needed for the request to work.

// commands/jira-helper.ts
import JiraClient from 'jira-client'
import { config } from 'dotenv'

config()

const jiraConfig = {
  host: process.env.JIRA_HOST!,
  username: process.env.JIRA_USER!,
  token: process.env.JIRA_API_TOKEN!,
  projectKey: process.env.JIRA_PROJECT_KEY!,
  issueType: process.env.JIRA_ISSUE_TYPE!,
  labels: ['automated', 'slack-bot']
}

const jira = new JiraClient({
  protocol: 'https',
  host: jiraConfig.host,
  username: jiraConfig.username,
  password: jiraConfig.token,
  apiVersion: '3',
  strictSSL: true
})

export const generateADFDescription = (text: string) => ({
  type: 'doc',
  version: 1,
  content: [
    {
      type: 'paragraph',
      content: [
        {
          type: 'text',
          text
        }
      ]
    }
  ]
})

export const addNewIssue = async (issueData: any) => {
  try {
    return await jira.addNewIssue(issueData)
  } catch (error) {
    console.error('Error creating Jira issue:', error)
    throw error
  }
}

export const getJiraConfig = () => jiraConfig

Next let’s wire up the actual slash command using Bolt:

import type { AllMiddlewareArgs, SlackCommandMiddlewareArgs } from '@slack/bolt'
import { format } from 'date-fns'
import { config } from 'dotenv'
import {
  addNewIssue,
  generateADFDescription,
  getJiraConfig
} from './jira-helper'

config()

const jiraCommandCallback = async ({
  ack,
  command,
  respond
}: AllMiddlewareArgs & SlackCommandMiddlewareArgs) => {
  try {
    await ack()

    const rawArgs = command.text.match(/`[^`]*`|\S+/g) || []
    const args = rawArgs.map((arg) => arg.replace(/^`(.*)`$/, '$1'))
    const actionType = args[0]

    switch (actionType) {
      case 'report':
        await respond(`Creating Jira report`)
        const title = args[1]
        const description = args[2]
        const summary = `${title} - ${format(
          new Date(),
          'yyyy-MM-dd HH:mm:ss'
        )}`

        const JiraConfig = getJiraConfig()
        const adfDescription = generateADFDescription(description)

        const issueData = {
          fields: {
            project: {
              key: JiraConfig.projectKey
            },
            summary,
            description: adfDescription,
            issuetype: {
              name: JiraConfig.issueType
            },
            labels: JiraConfig.labels
          }
        }

        const issue = await addNewIssue(issueData)

        await respond(
          `Jira ticket created: <https://${JiraConfig.host}/browse/${issue.key}|${issue.key}>`
        )
        break
      default:
        await respond('❌ Invalid action. Use `/jira report`.')
        break
    }
  } catch (error) {
    await respond('❌ Failed to create the JIRA ticket. Check logs or config.')
  }
}

export default jiraCommandCallback

Finally, in your /commands/index.ts folder add the callback:

// commands/index.ts
import type { App } from '@slack/bolt'
import sampleCommandCallback from './sample-command'
import JiraCommandCallback from './jira-command'

const register = (app: App) => {
  app.command('/sample-command', sampleCommandCallback)
  app.command('/jira', JiraCommandCallback)
}

export default { register }

Step 4: Test the integration

Now that you’ve wired up the command and connected Jira, it’s time to test it live. Run npm start to start the Slack bot, head over to any Slack channel where your bot is added and try the command:

/jira report `Failed checkout flow` `Checkout fails for guest users on Firefox`

You should see a response like: Jira ticket created: DEV-123. Open the Jira link and navigate to the ticket to verify the ticket details.

🎉 Boom - you just created a Jira ticket from Slack!

Jira ticket example

Step 5: Deployment

We implemented and tested the Slack bot locally, and in order for this to work beyond your local machine, you’ll need to host it somewhere public (so Slack can reach it) and configure it properly for production. I am not going to go into the details as part of this blog, but there are two options that you can use: deploy via socket mode or deploy via HTTP endpoint. You can use Render, AWS Lambda or AWS App Runner to do this. Read more about this in the official Slack documentation.

Summary

In just under an hour, we built a fully functional Slack automation bot, one that can respond to messages, accept slash commands, and even create Jira tickets from inside Slack. This isn’t just a cool side project, it’s a practical tool that streamlines developer workflows, reduces context-switching, and brings test reporting, CI status, and issue tracking closer to where your team already collaborates.

By leveraging tools like Slack’s Bolt, APIs and a bit of TypeScript, you’ve seen how fast it is to go from idea to real automation. The best part? You now have a foundation that can be extended with GitHub integrations, flaky test tracking, release status reports, or whatever your team needs most. SDETs today are enablers — and with automation like this, we can remove friction and let engineers focus on building great software. 🚀