Slack apps and bots are a handy way to automate and streamline repetitive tasks. Slack's Bolt framework consolidates the different mechanisms to capture and interact with Slack events into a single library.
From the Slack Bolt documentation:
All flavors of Bolt are equipped to help you build apps and integrations with our most commonly used features.
Install your app using OAuth 2.0
Receive real time events and messages with the Events API
Compose rich, interactive messages with Block Kit
Respond to slash commands, shortcuts, and interactive messages
Use a wide library of Web API methods
In my experience, this has made it straight forward to combine functionality instead of having multiple apps all handling different elements of Slack's API ecosystem.
There is a ton that you can do combining this library with the Amplify toolchain. So, let's create an app using Python ๐!
We'll create a simple Slack app that's triggered when the /start-process
slash command is run from within Slack.
Set up your Amplify project
This is the standard procedure. The project name used is slackamplify
.
? Enter a name for the project slackamplify
The following configuration will be applied:
Project information
| Name: slackamplify
| Environment: dev
| Default editor: Visual Studio Code
| App type: javascript
| Javascript framework: none
| Source Directory Path: src
| Distribution Directory Path: dist
| Build Command: npm run-script build
| Start Command: npm run-script start
Add a REST API with a Lambda function
The REST API is the lifeline of the Slack app. This API will be called each time the slash command is invoked from within Slack.
amplify add api
For the API path, I use a base path (/
) below. If you something specific here, make sure to append that path to your base URL endpoint when configuring the Slack app below.
? Please select from one of the below mentioned services: REST
? Provide a friendly name for your resource to be used as a label for this category in the project: slackpython
? Provide a path (e.g., /book/{isbn}): /
? Choose a Lambda source Create a new Lambda function
? Provide an AWS Lambda function name: slackpythonfunction
? Choose the runtime that you want to use: Python
Only one template found - using Hello World by default.
Available advanced settings:
- Resource access permissions
- Scheduled recurring invocation
- Lambda layers configuration
? Do you want to configure advanced settings? No
? Do you want to edit the local lambda function now? No
Successfully added resource slackpythonfunction locally.
Set up the function's virtual environment
On to the function!
But first, we need to update the function's Python virtual environment.
Install the Python dependencies
The next step is very important!
Change into the function's directory!
cd <project>/amplify/backend/function/<function-name>
To install dependencies, you'll need to first activate the Python virtual environment (below) for this function. To do this, you need to be in the function directory (step above). I've seen this trip folks up when folks actually install dependencies into the global Python installation and not into the correct virtual environment. The Amplify CLI will will prep your function directory for Pipenv if you select the Python runtime for your function during the amplify add api
prompts.
TIP
A quick reminder - Pipenv differs slightly from other Python virtual environment tooling:
-
pipenv shell
activates the environment -
exit
deactivates the environment
Activate the virtual environment
pipenv shell
I like to think of each Amplify function as it's own isolated environment where the code (i.e. functionality) pairs 1:1
with the dependencies within that environment. And for each function, you can have your own isolated Python virtual environment environment.
Note
If you want to share dependencies, or code, across functions then you'll want to check out the Lambda Layers capability in the Amplify CLI to see if that fits your use case.
Now, install the required dependencies:
pipenv install slack_bolt
After installing the first dependency, you will see a Pipfile.lock file in the directory.
pipenv install python-lambda
The Pipfile will now list the above dependencies that you installed.
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
[packages]
src = {editable = true, path = "./src"}
+ slack-bolt = "*"
+ python-lambda = "*"
[requires]
python_version = "3.8"
Update the Lambda function
At this point, we'll use the the placeholder Lambda function from the Example with AWS Lambda in the Bolt documentation. It may be collapsed and at the bottom of the page, so double check that you're looking at the right snippet.
# reference example from: https://slack.dev/bolt-python/concepts#lazy-listeners
from slack_bolt import App
from slack_bolt.adapter.aws_lambda import SlackRequestHandler
# process_before_response must be True when running on FaaS
app = App(process_before_response=True)
def respond_to_slack_within_3_seconds(body, ack):
text = body.get("text")
if text is None or len(text) == 0:
ack("Usage: /start-process (description here)")
else:
ack(f"Accepted! (task: {body['text']})")
import time
def run_long_process(respond, body):
time.sleep(5) # longer than 3 seconds
respond(f"Completed! (task: {body['text']})")
app.command("/start-process")(
ack=respond_to_slack_within_3_seconds, # responsible for calling `ack()`
lazy=[run_long_process] # unable to call `ack()` / can have multiple functions
)
def handler(event, context):
slack_handler = SlackRequestHandler(app=app)
return slack_handler.handle(event, context)
I recommend reading through this GitHub thread to get a better understanding of process_before_response
, ack()
, and lazy=
in the code above. These elements make it possible to run this an application like this in a serverless environment without the need for a persistent long-running server.
Adjusting the function's IAM policy
You'll need to add the adjust policy below for your Lambda function as mentioned in the Bolt docs.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"lambda:InvokeFunction",
"lambda:GetFunction"
],
"Resource": "*"
}
]
}
To do so, adjust the lambdaexecutionpolicy
in the /amplify/backend/function/<function-name>/<function-name>-cloudformation-template.json to add the above statement.
"lambdaexecutionpolicy": {
"DependsOn": ["LambdaExecutionRole"],
"Type": "AWS::IAM::Policy",
"Properties": {
"PolicyName": "lambda-execution-policy",
"Roles": [{ "Ref": "LambdaExecutionRole" }],
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"],
"Resource": { "Fn::Sub": [ "arn:aws:logs:${region}:${account}:log-group:/aws/lambda/${lambda}:log-stream:*", { "region": {"Ref": "AWS::Region"}, "account": {"Ref": "AWS::AccountId"}, "lambda": {"Ref": "LambdaFunction"}} ]}
},
+ {
+ "Sid": "VisualEditor0",
+ "Effect": "Allow",
+ "Action": [
+ "lambda:InvokeFunction",
+ "lambda:GetFunction"
+ ],
+ "Resource": "*"
+ }
]
}
}
}
Push the API
amplify push
And create the resources ๐
โ Successfully pulled backend environment dev from the cloud.
Current Environment: dev
| Category | Resource name | Operation | Provider plugin |
| -------- | ------------------- | --------- | ----------------- |
| Function | slackpythonfunction | Create | awscloudformation |
| Api | slackpython | Create | awscloudformation |
? Are you sure you want to continue? (Y/n) Yes
Once this is deployed, the URL can be used in the next step (Create the Slack app).
REST API endpoint: https://<id>.execute-api.<region>.amazonaws.com/dev
We now have 90% of the Slack app deployed. The app needs environment variables to be complete but we can only get those once we create a new app in Slack. So, let's do that.
Create the Slack app
Now, you'll need to head over to Slack to create (if it doesn't exist) and link the app to the Amplify API.
- Go to https://api.slack.com/apps and Create new app > From scratch. Use the endpoint from the API above!
- Select Name & workspace
- Basic information > Add features & functionality > Slash commands
- Basic information > Install your app
Update the Lambda function's environment variables
Slack will generate the required tokens and secrets that you'll need to populate into your function with environment variables once the app and command are set up.
Add the below variable/token pairs to the function in Lambda. To add environment variables, go to Configuration > Environment variables in the AWS Console for the Lambda function.
-
SLACK_SIGNING_SECRET
: Basic information > App credentials -
SLACK_BOT_TOKEN
: OAuth & Permissions > starts withxoxo-
Test the command in Slack
Awesome! Now let's test the slash command.
It shows in the command menu.
And...the command runs as expected when submitted along with some data.
Conclusion
There you have it! A Slack app running in Lambda created with AWS Amplify running in a serverless context.
I'd definitely take a look at the other ways that you can listen and respond to messages. My favorite? Responding to reactions...
@app.message(":wave:")
def say_hello(message, say):
user = message['user']
say(f"Hi there, <@{user}>!")
TIP
Once you start listening for other events, you'll need to verify your endpoint configuration. For example, some events require that the endpoint URL end with /slack/events
.
Hope that helps! ๐
If you have any questions or feedback, feel free to reach out on X @siegerts.
Subscribe to get my latest content by email -> Newsletter
Top comments (0)