Building Slack Apps with Tines: Part 2
Recap
Other posts in this series:
As of our last post, we now have the foundation for our Slack app in place. We've added a few scopes, created a dedicated channel (to which we've added our bot), and can send simple DM's to this channel. This is all well and good, but right now this conversation is feeling a bit one-sided, isn't it? If our users try to speak to or interact with our bot in any way, we have no way of knowing - Let alone responding to this desired interactivity.
Slack Interactivity
Sending one-way Slack notifications is fine in many situations. If your bot just needs to check in from time to time to say "Hey, I did X" or "Heads up, Y just occurred", then introducing interactivity might not be advisable. But if you'd like to do more, then we need to entertain the idea of two-way communication. Let's explore how we might best accomplish this.
Slash Commands
If you've interacted with Slack at all, you've likely seen slash commands in action. You invoke them using forward slashes followed by a keyword, like so:
/command argument
Slash commands are fantastic if the initial interaction begins with your end user. For example, take Help Desk interactions. You might want to enable your users to see tickets they've opened based on status:
/mytickets open
Or maybe you'd like them to be able to open a ticket from Slack:
/openticket "My computer has fallen and can't get up"
These are based on real-world examples many have implemented either via open-source or commercially-available products. Help Desk bots are all over the place and for good reason - Slack makes building and implementing a bot of this nature extremely accessible.
Block Actions
When it comes to adding interactivity to your Slack DM's you'll have quite a few choices:
- Buttons
- Checkboxes
- Datepickers
- Multi Selects
- Single Selects
- Overflows
- Plaintext Inputs
- Radio Buttons
Here's how a flow using block actions might look: 1. Your bot sends a DM to a channel containing a set of buttons 2. Your user clicks one of the presented buttons 3. A Webhook you've setup catches the response 4. Your App then reacts accordingly based on the response 5. Optionally (but recommended), you update the original Slack DM as well so the user receives feedback on their decision
Say, for example, your bot wants to process a Fantasy Football trade on your behalf. Being more of a hands-on Fantasy Football Manager, you don't want the bot to do this without your say-so. Your bot would send a DM containing the trade details to your channel and two buttons 'Approve' and 'Deny'. Depending on what you select, your bot would act accordingly.
Block Actions can be a little more versatile than Slash Commands, but that's not to say you can't trigger a DM containing Block Actions using a Slash Command. Slack Interactivity isn't an either/or decision - Feel free to mix and match based on what's right for your App.
Clearing Things Up
Concerned about which you should choose? Remember: You can use both! Base your flavor of interactivity based on your use case.
Still unsure about what type of interactivity is right for your use case? Here's the tl;dr:
- Use Slash Commands if you want your users to initiate the interaction
- Use Block Actions if you want your bot to initiate the interaction
I highly recommend exploring both types to see what works best for you.
Block Kit Builder
No tutorial that touches on Slack DM's would be complete without at least mentioning Block Kit Builder. It's free to use and is as feature-rich as can be. Similar to HTML WYSIWYG (What You See Is What You Get) editors, you simply select which components you'd like (and delete the ones you don't) and the raw array of blocks on the right will adjust itself accordingly. Inversely, modifying the raw JSON will modify its GUI representation in real time.
Block Kit Builder will keep you sane, particularly when your DM requires far too many blocks to keep track of manually.
Catching Requests
Whichever type of interactivity your use case requires, your App will need to be aware of interactive invocations. This inevitably calls for at least one Webhook (and often more) so you have the means to accept interactive payloads, process them, and respond accordingly.
Tines Webhooks
As far as Tines Actions go, I would venture to say Webhooks are second only to HTTP Requests in terms of utility. Introducing interactivity to your App is literally as simple as dragging and dropping a Webhook into your Story, copying its URL, and placing it in the right place (regardless if your chosen platform is Slack or elsewhere).
All Together Now
Alright, we've got the building blocks down - Let's see what we can do with what we've learned.
Bring on the Dogs
Let's setup our first Slash Command. Log in to Tines and navigate to the Story you setup in part one (or if you're just joining us, create a brand new Story to work with). Drag and drop a Tines Webhook into your Story and give it a good name. Grab the 'Webhook URL' listed in the right sidebar - We'll need that soon.
Next, head over to your Slack App (which should be listed here). Select 'Slash Commands' from the sidebar, then click 'Create New Command'.
Decide on your command's name (ideally something unique and intuitive for your users) and fill in your other details. Paste the Webhook URL we just copied into 'Request URL'.
We are well on our way! Now when our users use the /list-all-dogs
command, Tines will receive a Slack payload pertinent to that interaction. But that isn't super interesting in and of itself - Tines needs to do something with that payload. Let's work on that next.
Head back over to your Tines Story and drag over an HTTP Request
action. Name it 'List All Dogs' and drop https://dog.ceo/api/breeds/list/all
* into the URL field. Connect your Webhook to your this HTTP Request
action.
Now we need to respond to our Slack user. Drag and drop another HTTP Request
action onto your canvas, connect your last HTTP Request (List All Dogs
) and configure it according to the below parameters. Remember, anything appearing between '<<>>' indicates a Resource, Credential, or Tag value. You can add these with the key combination 'ctrl + space' on Windows, or 'cmd + space' on Mac.
- URL: https://slack.com/api/chat.postMessage
- Content Type: JSON
- Method: POST
- Payload: {"channel": "<< RESOURCE.slack_ff_channel >>", "text": "<< list_all_dogs.body.message |> KEYS(%) |> JOIN(%, \"\n\") >>"}
- Headers: {"Authorization": "Bearer << CREDENTIAL.slack_api_token >>"}
When all lis said and done you should wind up with something that looks a bit like this:
Let's talk about the text in our payload for a moment. You'll notice we're piping the output from the message key from the List All Dogs
action into the KEYS()
formula, then piping the array we get into the JOIN()
formula. If you're coming from Python, this is the equivalent of '\n'.join(object.keys())
.
Essentially we're talking the object we receive, extracting the keys into an array of strings, and then joining those strings with newlines. Take a look at how it looks now when we invoke this command in our Slack environment:
If you've been following along, you now have a working start-to-finish example of a Slack Slash Command in action - Not too shabby!
*Note: This is a free, public API we're using for this example. The Dog CEO API is self-described as "the internet's biggest collection of open source dog pictures". If you'd like to check out the rest of their content (and I highly rceommend you do), head on over to https://dog.ceo/
Trading with Tines
There are a few more steps required for this next technique, but the results are well worth the effort.
First, create another Webhook* in Tines. Name it something meaningful and copy the Webhook URL - We'll need it shortly.
Head back to your Slack App's configuration page and select 'Interactivity and Shortcuts' from the sidebar. Toggle 'Interactivity' to 'On' and paste your Webhook's URL into the 'Request URL' field. Finally, click 'Save Changes' at the bottom of the page.
Now when a user interacts with a DM your bot has sent, Slack will relay the corresponding payload to the Webhook we just set up. To put this functionality to the test, we'll implement the Fantasy Football scenario we proposed earlier. First, we need to create an interactive DM using Slack's Block Kit Builder. Feel free to use the example blocks I've provided below:
[
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Trade Proposal*\nAdd: Peter Venkman\nDrop: Ray Stantz"
}
},
{
"type": "actions",
"elements": [
{
"type": "button",
"style": "primary",
"text": {
"type": "plain_text",
"text": "Approve",
"emoji": true
},
"value": "approve_trade",
"action_id": "approve_trade"
},
{
"type": "button",
"style": "danger",
"text": {
"type": "plain_text",
"text": "Deny",
"emoji": true
},
"value": "deny_trade",
"action_id": "deny_trade"
}
]
}
]
Head back over to your Tines Story and add a new HTTP Request
action. We want to add a key called "blocks" to the action payload and use the blocks we created above as its value. So when we're done, it'll look something like this:
Click 'Run' to send this DM to Slack. If all goes well, we should see the following:
Feel free to click either option, but I'm inclined to approve this one. Back in Tines we should find a new Event ready for us to process. How you choose to handle your interactive payloads from here is largely your decision, but I highly recommend doing the following at the bare minimum:
- Add an Event Transform to JSONify your received payload (which may look something like
receive_slack_interactivity.body.payload |> JSON_PARSE(%)
) - Update your original DM so your users know their response has been received using Slack's chat.update method
With these changes in mind, our automation may look something like this...
Which automatically updates our DM to this:
This isn't a complete example, of course - In the real world we'd add a Trigger
action to add an 'If/Else' condition depending on if a user approves or denies this kind of proposal. Each branch would then introduce its own business logic to handle those decisions appropriately.
*Note: Avoid re-using the Webhook you created to accept Slash Command payloads.
Closing Thoughts, Parting Bots
Now that we've laid the groundwork for two-way communication, we've drastically expanded our App's potential overall functionality and possible use cases. Those use cases often require interaction with an additional externality on which we've only briefly skimmed, however: Third-party APIs. Although which API you choose will ultimately depend on your particular goals, we'll pick out a couple of examples that may be of interest in Part Three. Until then, be curious, build frequently, and iterate often.