Open tasks from Slack and post questions back

The tasks open on the team's delivery line, and a reply in a question's Slack thread answers it. You need:

  • A Slack app you can configure, installed in your Slack workspace.
  • Terraform set up with the Spotlight provider, using a token that has the configure and secrets scopes. See Manage Spotlight with Terraform.
  • A team with a line whose role is delivery and a station keyed triage, as the built-in Delivery line has.
  • A Spotlight deployment that allows outbound calls to slack.com, since Spotlight calls Slack's API from its own servers.

Prepare the Slack app

In the Slack app's settings:

Give the app's bot the scopes app_mentions:read, chat:write and channels:history, and reinstall the app.

Copy the app's bot token and its signing secret.

Note the ID of the Slack channel that questions from other tasks should go to.

Keep the two values as secrets

Keep the signing secret without bindings: the integration uses it to check that deliveries come from Slack. Bind the bot token to slack.com, in the authorization header.

variable "slack_signing_secret" {
  type      = string
  sensitive = true
}

variable "slack_bot_token" {
  type      = string
  sensitive = true
}

data "spotlight_workspace" "acme" {
  slug = "acme"
}

resource "spotlight_secret" "slack_signing_secret" {
  workspace_id     = data.spotlight_workspace.acme.id
  name             = "SLACK_SIGNING_SECRET"
  value_wo         = var.slack_signing_secret
  value_wo_version = 1
}

resource "spotlight_secret" "slack_bot_token" {
  workspace_id = data.spotlight_workspace.acme.id
  name         = "SLACK_BOT_TOKEN"
  bindings = [
    { host = "slack.com", carrier = "header", name = "authorization", prefix = "Bearer " },
  ]
  value_wo         = var.slack_bot_token
  value_wo_version = 1
}

Define the integration

A task opened from Slack posts its questions to the channel it was opened from. routes names the channel for questions from a line's other tasks, by the line's key (delivery is the built-in Delivery line's key): replace <channel id> with the channel you noted.

data "spotlight_team" "core" {
  workspace_id = data.spotlight_workspace.acme.id
  key          = "core"
}

resource "spotlight_integration" "slack" {
  team_id = data.spotlight_team.core.id
  definition = {
    integration = "slack"
    name        = "Slack"

    webhook = {
      verify = {
        hmac = {
          algorithm = "sha256"
          secret    = "secret.${spotlight_secret.slack_signing_secret.name}"
          header    = "x-slack-signature"
          prefix    = "v0="
          signed    = "$${{ \"v0:\" + headers[\"x-slack-request-timestamp\"] + \":\" + raw }}"
          freshness = {
            at     = "$${{ headers[\"x-slack-request-timestamp\"] }}"
            within = "300s"
          }
        }
      }
      respond = {
        when = "$${{ body.type == \"url_verification\" }}"
        body = "$${{ body.challenge }}"
      }
      mine = "$${{ body.event.bot_id != \"\" }}"
      events = [
        {
          when = "$${{ body.event.type == \"app_mention\" }}"
          raise = {
            happening = "slack.app_mention"
            label     = "Mentioned"
            told      = "Mentioned Spotlight in Slack."
            opens = {
              onto  = "delivery"
              at    = "triage"
              title = "$${{ body.event.text }}"
            }
            marks = [
              { kind = "slack.thread", value = "$${{ body.event.channel + \":\" + (has(body.event.thread_ts) ? body.event.thread_ts : body.event.ts) }}" },
              { kind = "slack.channel", find = false, value = "$${{ body.event.channel }}" },
            ]
          }
        },
        {
          when = "$${{ body.event.type == \"message\" && body.event.thread_ts != \"\" }}"
          raise = {
            happening = "slack.message.replied"
            label     = "Replied"
            told      = "Replied in a Slack thread."
            spoken    = true
            say       = "$${{ body.event.text }}"
            marks = [
              { kind = "slack.thread", value = "$${{ body.event.channel + \":\" + body.event.thread_ts }}" },
            ]
          }
        },
      ]
    }

    actions = {
      post-message = {
        request = {
          method = "POST"
          url    = "https://slack.com/api/chat.postMessage"
          auth   = { secrets = ["secret.${spotlight_secret.slack_bot_token.name}"] }
          body   = "$${{ {\"channel\": target.channel, \"text\": ask.body} }}"
          expect = { success = "$${{ response.ok }}" }
          capture = {
            thread = "$${{ target.channel + \":\" + response.ts }}"
          }
        }
      }
    }

    on = {
      "task.asked" = {
        do = "post-message"
        to = {
          channel = "$${{ task.marks[\"slack.channel\"] }}"
          thread  = "$${{ task.marks[\"slack.thread\"] }}"
        }
        routes = {
          delivery = "<channel id>"
        }
        mark = [
          { kind = "slack.thread", value = "$${{ capture.thread }}" },
        ]
      }
    }
  }
}

onto names the line's role, and at the key of the station new tasks start at. The keys under routes are line keys. Change any of them that differ on your team, and if your team's key is not core, change it in spotlight_team. Run terraform apply. If the plan fails at an expression, Expressions lists what each one can read.

Point Slack at the team

Slack sends events to the team's webhook address:

https://app.usespotlight.ai/api/webhooks/w/<workspace slug>/t/<team key>/slack

On a self-hosted Spotlight, the address starts with your deployment's address instead. <workspace slug> is the part after /w/ in the workspace's address, and <team key> is the key of the team that holds the integration (core in the example above).

In the Slack app's event subscription settings:

Turn events on and give the address as the request URL. Slack sends a challenge, Spotlight responds to it, and Slack marks the address verified.

Subscribe the bot to the app_mention and message.channels events.

Save, and reinstall the app if Slack asks.

Check it

Mention the app in a channel it has joined. A task opens at Triage on the delivery line, titled with the message. When the task asks a question, the question appears in the channel, and a reply in its thread answers it and resumes the work.

On this page