In this tutorial, we'll be creating a Workflow Builder step for you and your teammates to use. Adding this step to a Workflow will send news articles to a specified channel, based on some kind of preset trigger. Here's an example of what your app will post:
Workflow Builder is a no-code set of tools to automate tasks within Slack. In essence, a Workflow consists of a single trigger, followed by one or more steps. The built-in steps that you can use are:
In addition to the built-in steps, it is also possible to ✨ create your own custom step ✨ , which is what we'll be doing in this tutorial. If you want to know more about Workflow Builder itself, take a look at our Help Center documentation or watch the video below.
Before we start, let's cover some of the use cases that this app could be useful for.
This is how your app will look once it is finished:
The code for this app can be found on this GitHub repository. Clone the repository so that you can have a local version to work with.
You can get a News API key for free if you are creating an app for development purposes. On the sign up page, fill in your name, e-mail address, and password, then choose "I am an individual" and submit the form. You will be granted an API key, which you should keep in a safe spot as we'll use this to set the NEWS_API_KEY
environment variable later in this tutorial. Note that the News API free plan has a limit of 100 requests per day.
You can create a Slack app from the link above and doing so from this link will automatically add the correct scopes needed for your app to function. Just choose which workspace that you would like it to live on.
Once this is done, you'll need to issue an App-Level token which allows for you to use Socket Mode, a feature that allows you to send and receive payloads securely over a WebSocket. To do this, scroll down to App-Level Tokens
and press the Generate Token and Scopes button. Give it a name and grant it the connections:write
scope and press Generate. Copy the value and keep it safe as we'll use it to set the SLACK_APP_TOKEN
environment variable.
Next, you'll need to install your app in your workspace. Doing so will create a bot token, which will allow your Slack app to call the Slack API. In the left-hand sidebar, head to Install App
and then hit the Install to Workspace
button and follow the prompts. After you've been redirected, copy the value of your Bot User OAuth Token. This will be used to set the value of SLACK_BOT_TOKEN
in the next step.
Set up your Python environment by installing all the required libraries. This can be done by utilizing the requirements.txt
file found in the repository. You'll also need to set the values of the following variables that you obtained in steps above as environment variables so that your Slack app can access them. Make sure that you don't mix up SLACK_APP_TOKEN
with SLACK_BOT_TOKEN
as they look similar! Your SLACK_APP_TOKEN
will start with xapp-
and your SLACK_BOT_TOKEN
will start with xoxb-
.
# Setting up your python environment
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
# Set your API keys and tokens from the steps above as environment variables
export NEWS_API_KEY=as2fs...
export SLACK_APP_TOKEN=xapp-...
export SLACK_BOT_TOKEN=xoxb-...
You can use the following command to start your app:
python app.py
Now that we have everything setup for success, we'll need to define the following three functions in your code: edit
, save
and execute
. The edit
function will be called whenever your workflow step is edited and will be a place to configure your step. After that, we'll implement the save
function, which will define what inputs and outputs we want from our step. Lastly, we'll build the execute
, which is where the actual functionality of your app will happen -- in our case calling the News API. Let's get started.
This is where you define the configuration parameters of your custom step, shown above. You can use the Block Kit Builder to mock out what this modal will look like and how it will gather information from the user. One thing to note is that this function is called not only when the user first opens up your step, but also on subsequent opens of the modal as well. For that reason, you’ll need to make sure that you repopulate the modal with the previous input. You can find more details on the edit
function within the API documentation.
Following is an example of what this looks like for just the query block in this app. Note that the rest of the code has been removed for clarity.
def edit(ack: Ack, step: dict, configure: Configure):
ack()
inputs = step.get("inputs")
blocks = []
# Block for users to enter a search query
query_block = {
"type": "input",
"block_id": input_query,
"optional": True,
"element": {
"type": "plain_text_input",
"action_id": "_",
"placeholder": {
"type": "plain_text",
"text": "technology, sports, finance (Separate multiple terms by a comma)",
},
},
"label": {
"type": "plain_text",
"text": "Query (Leave blank for all news articles)",
},
}
# Re-populates your configuration blocks with the previously filled out values when editing your workflow step
if input_query in inputs:
value = inputs.get(input_query).get("value")
if value is not None:
query_block["element"]["initial_value"] = value
# Add your blocks in the order that you would like them to show up in the modal
blocks.append(query_block)
# Pass your blocks to the `configure` function and it will handle the rest
configure(blocks=blocks)
This function is where you retrieve the values that the user inputs into your modal from the edit
stage. This particular function is run whenever a user presses the green Save
button. The inputs
dictionary represents the data your app expects to receive from the user upon workflow step execution (i.e. what is shown from the edit
modal). The outputs
dictionary is a list of objects containing data that your app will provide upon the workflow step’s completion which can be used in subsequent steps of your workflow. More details can be found within our API documentation. Again in this snippet, some code has been removed for clarity.
state_values = view["state"]["values"]
# Extracts the values found within the `state` parameter
query = _extract(state_values, input_query, "value")
update(
# Defines what is required from the user for this Workflow step
inputs={
input_query: {"value": query},
},
# Defines what will be produced when this step is properly completed
outputs=[
{
"name": channel_id,
"type": "text",
"label": "Posted message timestamp",
}
for channel_id in channels
],
)
ack()
This particular function is different from the two before it and is run whenever a workflow containing your step is run. In this tutorial, this is where any logic associated with interacting with a third-party API lives. Fetch the needed data and bring it back to this function for further processing. In this case, we’re calling the News API for some articles and then sending a message into Slack with this information. For the full details on this method, visit the documentation page.
def execute(step: dict, client: WebClient, complete: Complete, fail: Fail):
inputs = step.get("inputs", {})
try:
query = inputs.get(input_query).get("value")
num_articles = int(inputs.get(input_num_articles).get("value"))
channels = inputs.get(input_channel_ids).get("value").split(",")
# Calls the third-party News API and retrieves articles using inputs from above
articles = fetch_articles(news_api_key, query, num_articles)
except Exception as err:
fail(error={"message": f"Failed to fetch news articles ({err})"})
return
outputs = {}
try:
if articles:
for article in articles:
blocks = format_article(article)
for channel in channels:
# Send a message to all the specified channels
response = client.chat_postMessage(
channel=channel,
blocks=blocks,
unfurl_links=False,
unfurl_media=False,
text=article.title,
)
outputs[channel] = response.get("message").get("ts")
else:
# Notify the user that no articles were found.
for channel in channels:
response = client.chat_postMessage(
channel=channel,
text=f"No articles matched your query: {query}.",
)
outputs[channel] = response.get("message").get("ts")
except SlackClientError as err:
fail(error={"message": f"Notification failed ({err})"})
complete(outputs=outputs)
Now that we're done coding, all that's left is to create a workflow and add your newly created step. If you need detailed instructions on how to do this, check this Help Center article. Otherwise, create a workflow with whichever kind of trigger that you want. In this example, a shortcut trigger was used so that we can test it easily. Don't forget to add in your newly created step!
Once you have all the functions defined and your workflow created, you're good to go! Within Slack, click on the lightning bolt icon in the bottom left hand corner, type "News Please!" and hit Enter. If everything is set up correctly, you'll see some news articles pop up for your reading pleasure. You can also modify the trigger to be on a schedule so that you can your favorite news every day at 9AM.