Systems Library / Operations & Admin / How to Build a Slack Workflow Automation System
Operations & Admin communication

How to Build a Slack Workflow Automation System

Automate business processes directly within Slack using workflows.

Jay Banlasan

Jay Banlasan

The AI Systems Guy

Your team already lives in Slack. I built a slack workflow automation system that triggers business processes from Slack messages, commands, and reactions. Submit expenses with a slash command. Approve requests with a button click. No context switching to another tool.

Slack becomes the interface for your operations.

What You Need Before Starting

Step 1: Set Up the Slack Bot

from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler

app = App(token="xoxb-your-bot-token")

Step 2: Build Slash Command Workflows

@app.command("/expense")
def handle_expense(ack, command, client):
    ack()
    client.views_open(
        trigger_id=command["trigger_id"],
        view={
            "type": "modal",
            "callback_id": "expense_submit",
            "title": {"type": "plain_text", "text": "Submit Expense"},
            "submit": {"type": "plain_text", "text": "Submit"},
            "blocks": [
                {
                    "type": "input", "block_id": "amount",
                    "element": {"type": "number_input", "is_decimal_allowed": True, "action_id": "amount_input"},
                    "label": {"type": "plain_text", "text": "Amount ($)"}
                },
                {
                    "type": "input", "block_id": "category",
                    "element": {
                        "type": "static_select", "action_id": "category_select",
                        "options": [
                            {"text": {"type": "plain_text", "text": "Travel"}, "value": "travel"},
                            {"text": {"type": "plain_text", "text": "Software"}, "value": "software"},
                            {"text": {"type": "plain_text", "text": "Office"}, "value": "office"}
                        ]
                    },
                    "label": {"type": "plain_text", "text": "Category"}
                },
                {
                    "type": "input", "block_id": "description",
                    "element": {"type": "plain_text_input", "action_id": "desc_input"},
                    "label": {"type": "plain_text", "text": "Description"}
                }
            ]
        }
    )

@app.view("expense_submit")
def handle_submission(ack, body, client):
    ack()
    values = body["view"]["state"]["values"]
    user = body["user"]["id"]
    amount = values["amount"]["amount_input"]["value"]
    category = values["category"]["category_select"]["selected_option"]["value"]
    description = values["description"]["desc_input"]["value"]

    client.chat_postMessage(
        channel="#finance-approvals",
        text=f"New expense from <@{user}>: ${amount} ({category})",
        blocks=[
            {"type": "section", "text": {"type": "mrkdwn",
                "text": f"*Expense Request*\nFrom: <@{user}>\nAmount: ${amount}\nCategory: {category}\nDescription: {description}"}},
            {"type": "actions", "elements": [
                {"type": "button", "text": {"type": "plain_text", "text": "Approve"},
                 "style": "primary", "action_id": "approve_expense", "value": f"{user}|{amount}"},
                {"type": "button", "text": {"type": "plain_text", "text": "Reject"},
                 "style": "danger", "action_id": "reject_expense", "value": f"{user}|{amount}"}
            ]}
        ]
    )

Step 3: Handle Approval Actions

@app.action("approve_expense")
def handle_approve(ack, body, client):
    ack()
    approver = body["user"]["id"]
    data = body["actions"][0]["value"].split("|")
    requester, amount = data[0], data[1]

    client.chat_postMessage(
        channel=requester,
        text=f"Your expense of ${amount} was approved by <@{approver}>."
    )

    client.chat_update(
        channel=body["channel"]["id"],
        ts=body["message"]["ts"],
        text=f"Expense ${amount} from <@{requester}> - APPROVED by <@{approver}>"
    )

@app.action("reject_expense")
def handle_reject(ack, body, client):
    ack()
    approver = body["user"]["id"]
    data = body["actions"][0]["value"].split("|")
    requester, amount = data[0], data[1]

    client.chat_postMessage(
        channel=requester,
        text=f"Your expense of ${amount} was rejected by <@{approver}>."
    )

Step 4: Add Reaction-Based Triggers

@app.event("reaction_added")
def handle_reaction(event, client):
    if event["reaction"] == "white_check_mark":
        channel = event["item"]["channel"]
        ts = event["item"]["ts"]

        result = client.conversations_history(channel=channel, latest=ts, inclusive=True, limit=1)
        message = result["messages"][0]

        if "action item:" in message.get("text", "").lower():
            client.chat_postMessage(
                channel=channel,
                thread_ts=ts,
                text=f"Action item marked complete by <@{event['user']}>."
            )

Step 5: Run the Bot

if __name__ == "__main__":
    handler = SocketModeHandler(app, "xapp-your-app-token")
    handler.start()

Deploy with systemd or a process manager:

# /etc/systemd/system/slack-bot.service
# [Service]
# ExecStart=/usr/bin/python3 /path/to/slack_bot.py
# Restart=always

What to Build Next

Add a /standup command that collects daily standup updates asynchronously. Team members respond on their own time, and the bot compiles a summary at 10am. Async standups save meeting time.

Related Reading

Want this system built for your business?

Get a free assessment. We will map every system your business needs and show you the ROI.

Get Your Free Assessment

Related Systems