Sending a direct message to a user in Slack
In this guide we'll cover how to create a self-serve Slack integration for a multi-tenant application using Knock. It assumes that you have already created a Slack app and created a Slack channel in Knock as outlined in the Slack integration overview guide.
In this implementation, your application's users will connect their Slack workspace to Knock and be able to send messages to individual users via DM. To make this easier to implement, we'll use Knock's SlackKit components to facilitate the OAuth flow.
Here's what we'll cover in this guide:
- Modeling a multi-tenant application in Knock using
Tenants
- Implementing a Slack OAuth flow using Knock's
SlackAuthButton
component - Using the
access_token
stored in Knock to request a user's Slack ID - Triggering a workflow with a user recipient to send a message to a DM
Key concepts
SlackKit connects multiple concepts in Knock to make it easier for your application's users to create a Slack integration. Tenants
are a concept you'll see throughout the following docs that are foundational to how SlackKit works, but might not be used in every implementation of Knock.
About tenants
Tenants in Knock are meant to represent groups of users who typically share the same resources. You might call these "accounts," "organizations," "workspaces," or something similar. In a standard SlackKit implementation, you'll store a Slack workspace's access_token
on a corresponding tenant in Knock.
If you already use Knock's tenant concept to power other 'account-based' features, you likely create tenants in Knock when an account or organization is created in your application. If you don't already use tenants in Knock, SlackKit can create tenants for you on the fly if they don't already exist.
Merging channel data
In this implementation, we'll actually store the required channel data for a SlackConnection
across two different entities in Knock: a Tenant
and an User
. This is because we want to store the access_token
for the Slack workspace on the Tenant
and the user_id
for the Slack user on the User
.
When you trigger a workflow using this recipient and tenant, Knock will merge the channel data from the Tenant
and the User
to send the message to the correct Slack DM channel. By storing the access_token
on the Tenant
, your customers only need to complete the OAuth flow once to connect their Slack workspace to Knock. From there, you can create UI that allows users to link their Slack user ID to their Knock user ID or automate this process during user registration.
Implementing SlackKit
To facilitate the OAuth flow and channel selection process, we'll use Knock's SlackKit components. SlackKit is a set of React components that make it easier to build Slack integrations in Knock. You can use SlackKit to build a self-serve Slack integration that allows your users to connect their Slack workspace to Knock.
Signing a user token
The only access you'll need to manage when using SlackKit are grants for your users to interact with their Tenant in Knock. This is necessary because the user in this context is an end user in your application who does not have access to Knock as a member of the account. Therefore, these grants provide them elevated privileges to operate on specific resources using the API.
We've made it easy for you to tell Knock which resources your users should have access to by making it a part of their user token. In this section you'll learn how to generate these grants using the Node SDK and, if you're not using the SDK, how to structure them for other languages.
You'll need to generate a token for your user that includes access to the tenant storing the Slack access token as well as any recipient objects storing Slack channel data described in this reference on SlackKit resource access grants.
Using the below example, you can quickly generate a token with the Node SDK.
You'll need to pass this token along with the public API key to the KnockProvider
that wraps KnockSlackProvider
and the rest of your components. We recommend storing the generated user token in local storage so that your client application has easy access to it.
Adding provider components
In order to give your components the data they need, they must be wrapped in the KnockSlackProvider
. We recommend putting this high in your component tree so that any Slack components that you use will be rendered within it. The Slack provider goes inside of the KnockProvider
. Your hierarchy will look like this:
The KnockSlackProvider
gives your components access to the status of the connection to your Slack app, so that they can all be in sync when a user is connecting, disconnecting, or experiencing a connection error.
SlackAuthButton
Implementing the OAuth flow with Your users will give your Slack app access to their own Slack workspaces via the SlackAuthButton
. This button can be used on its own, or nested in the SlackAuthContainer
for a bigger visual footprint.
Since we'll also be using this access_token
to resolve a user's email address to their Slack ID, we'll need to add some additional scopes to this OAuth request. We can do that by adding the users:read
and users:read.email
scopes to the SlackAuthButton
component with the additionalScopes
parameters.
Here's an example of how to use them:
The SlackAuthButton
maps a tenant in your product to a customer's Slack workspace. This means in most cases you'll just need a single instance of the SlackAuthButton
.
Remember to consider which roles in your application can access the SlackAuthButton
component. Knock does not control access to the component. In most cases, you'll add this connect button and container in the settings area of your product.
Resolving a user's Slack ID
To send a direct message to a user in Slack, you'll need to resolve their email address to their Slack ID. You can do this by making a request to the Slack API with the access_token
stored in Knock. Here's an example of how you might create a fetchUserId
function to do this using the Knock Node SDK:
We'll break this function down step-by-step:
Since your users have already connected their Slack workspace to Knock, you can use the knockClient.objects.getChannelData
method to get the access_token
for the user's tenant. Tenants in Knock are stored in a system-reserved object collection called $tenants
.
Once you have the tenant's access_token
, you can use it to make a request to the Slack API to resolve the user's email address to their Slack ID using the users.lookupByEmail
endpoint.
Assuming the request to the Slack API is successful, you can save the user's Slack ID as channel data on the user in Knock. This will allow you to send messages to the user's Slack DM channel.
Triggering a workflow
Once you have saved the user's Slack ID as channel data, you can trigger a workflow to send a message to that user's DM channel. Here's an example of how to trigger a workflow using the Knock Node SDK: