How to Build Your Own Simple Poll Slack App | Replit

How to Build Your Own Simple Poll Slack App

A guide by

Søren Rood

Introduction

Poll Slackbots are a great tool for sourcing async decisions, as well as having fun. For an off-the-shelf polling Slackbot, you may have to pay $100+ per month. It turns out, it's actually very easy to build and host on Replit for an order of magnitude less. This step-by-step guide will walk you through building and deploying your own polling Slack bot with no coding experience needed.

Final output of Slack poll bot

Getting started

To get started, fork the template below:

Use this template

Create a new Slack App

Go to the Slack API website and navigate to your apps dashboard. Click on "Create New App."

Select "From an app manifest". Select the Slack workspace you want to add your bot to.

{
    "display_information": {
        "name": "Poll Bot"
    },
    "features": {
        "bot_user": {
            "display_name": "Poll Bot",
            "always_online": false
        },
        "slash_commands": [{
            "command": "COMMAND_NAME",
            "url": "f'https://{url_domain}/slack/events'",
            "description": "Make a poll",
            "usage_hint": "Choose a superpower:, invisibility, flight, super strength, time travel",
            "should_escape": false
        }]
    },
    "oauth_config": {
        "scopes": {
            "bot": [
                "channels:history",
                "chat:write",
                "commands",
                "reactions:write"
            ]
        }
    },
    "settings": {
        "org_deploy_enabled": false,
        "socket_mode_enabled": false,
        "token_rotation_enabled": false
    }
}

Paste the code above from manifest.json into the manifest file on Slack. This manifest already includes the correct request_url field with the Replit deployment URL, appended with /slack/events.

Install the app to the workspace

On your app page, scroll down until you see the ability to "Install App to Workspace." Follow that process.

Add secrets

Scroll down to the "App credentials" section, and copy and paste the following credentials into the Secrets pane in Replit:

Building the bot

Import packages

Begin by importing the correct packages into main.py:

import logging
import os
from flask import Flask, request, render_template
from slack_bolt import App
from slack_bolt.adapter.flask import SlackRequestHandler
from emojis import random_emojis

Slackbot setup

First, we are going to establish some demand-related constants:

COMMAND_NAME = '/poll'
COMMAND_DELIMITER = ','

Second, we will set up some logging. This is optional, but it can be helpful information for debugging:

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

Set up command handler

Next, we need to set up route that handles the initial request to the bot. We will call this the command handler.

@app.command(COMMAND_NAME)
def handle_command(ack, body, say, client):
    ack()
    emojis = random_emojis()
    user_id = body['user_id']
    text = body.get('text', '')
    logger.info('Received command: %s', text)
    parts = text.split(COMMAND_DELIMITER)
    question = parts[0]
    options = [part.strip() for part in parts[1:] if part.strip()]
    if len(options) < 2:
        say("Please provide at least two options for the poll.")
        return
    blocks = []
    blocks.append({
        'type': 'header',
        "text": {
            "type": "plain_text",
            "text": f"{question}",
            "emoji": True
        }
    })
    emojis_used = []
    for i, option in enumerate(options):
        if i >= len(emojis):
            say(f"Only up to {len(emojis)} options are supported.")
            return
        blocks.append({
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": f":{emojis[i]}: {option}"
            }
        })
        emojis_used.append(emojis[i])
    blocks.append({"type": "divider"})
    blocks.append({
        "type": "section",
        "text": {
            "type": "mrkdwn",
            "text": f"Poll created by <@{user_id}>! Use the reactions to vote. ❤️ <https://replit.com|Replit>"
        }
    })
    response = client.chat_postMessage(channel=body['channel_id'], blocks=blocks, unfurl_links=False)
    message_ts = response['ts']
    for emoji in emojis_used:
        client.reactions_add(channel=body['channel_id'], name=emoji, timestamp=message_ts)

Set up an error handler

We want to make sure we capture requests that do not work, so we will create an error handler:

@app.error
def custom_error_handler(error, body, logger):
    logger.exception(f"Error: {error}")
    logger.info(f"Request body: {body}")

Set up the Flask server

We need a server for this bot, so we will use a common approach called Flask. Here is the code:

flask_app = Flask(__name__)
flask_app.config['JSON_AS_ASCII'] = False
handler = SlackRequestHandler(app)

@flask_app.route("/slack/events", methods=["POST"])
def slack_events():
    if bot_token == 'UNSET' or signing_secret == 'UNSET':
        return "Error: Missing environment variables", 500
    logger.debug("Received Slack event")
    return handler.handle(request)

@flask_app.route("/health", methods=["GET"])
def health_check():
    return "OK", 200

@flask_app.route("/", methods=["GET"])
def index():
    replit_deployment_domains = os.getenv('REPLIT_DOMAINS')
    replit_deployment_domain = replit_deployment_domains.strip().split(',')[0] if replit_deployment_domains is not None else None
    replit_dev_domain = os.getenv('REPLIT_DEV_DOMAIN')
    url_domain = replit_deployment_domain if replit_deployment_domain is not None else replit_dev_domain
    manifest = None
    if url_domain is not None:
        manifest = {
            "display_information": {
                "name": "Poll Bot"
            },
            "features": {
                "bot_user": {
                    "display_name": "Poll Bot",
                    "always_online": false
                },
                "slash_commands": [{
                    "command": COMMAND_NAME,
                    "url": f"https://{url_domain}/slack/events",
                    "description": "Make a poll",
                    "usage_hint": "Choose a superpower:, invisibility, flight, super strength, time travel",
                    "should_escape": false
                }]
            },
            "oauth_config": {
                "scopes": {
                    "bot": [
                        "channels:history",
                        "chat:write",
                        "commands",
                        "reactions:write"
                    ]
                }
            },
            "settings": {
                "org_deploy_enabled": false,
                "socket_mode_enabled": false,
                "token_rotation_enabled": false
            }
        }

secrets_defined = bot_token != 'UNSET' and signing_secret != 'UNSET'

return render_template('index.html', is_deployed=replit_deployment_domain is not None, manifest=manifest, secrets_defined=secrets_defined)

Finally, we need to add some code that starts the server when the app is run:

if __name__ == "__main__":
    flask_app.run(debug=True, host='0.0.0.0', port=int(os.environ.get('PORT', 3000)))

Deploy your bot

Finally, deploy your bot to a Reserved VM. Make sure to select "Background worker."

IMPORTANT NOTE: Once your application is deployed, you need to add your deployed URL to the Secret defined as REPLIT_DOMAINS. Then Redeploy the application.

On the next screen, click Approve and configure build settings most internal bots work fine with the default machine settings but if you need more power later, you can always come back and change these settings later. You can monitor your usage and billing at any time at: replit.com/usage.

Finally, click Deploy and watch your bot go live!

What's Next

If you’d like to bring this project and similar templates into your team, set some time here with the Replit team for a quick demo of Replit Teams.

Happy coding!