How to Build Your Own Simple Poll Slack App | Replit
How to Build Your Own Simple Poll Slack App
A guide by
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:
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:
- SLACK_SIGNING_SECRET - The "signing secret" for your app, found on the "Basic Information Page"
- SLACK_APP_TOKEN - The app-level token, found under "App Credentials" on the "Basic Information" page
- SLACK_BOT_TOKEN - The bot token for your app, found on the "OAuth & Permissions" page
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!