How Calendly makes scheduling smarter in Slack
At Calendly, we aim to make scheduling easy. You keep ownership of your calendar and time while sharing your availability with others, simply by sharing your Calendly link. But to make it as easy as possible, Calendly needs to be wherever people get their work done, and for many people, they get their work done in Slack.
Slack has transformed workplace communication and made working together easier and more productive. We heard from our users and from Slack that now more than ever, people want the simplicity of Calendly scheduling seamlessly integrated into Slack.
Slack released Slack Connect, which will further transform the way we work with people outside of our organization by opening up externally facing channels of communication. Calendly’s focus is to help people outside of their own organization connect with each other. Naturally as Slack pushes forward with Slack Connect, we felt that Calendly should be an integral application to help users be more productive when communicating outside of their organization.
Keeping it simple
We want to make sure using Calendly in Slack would be easy and intuitive. There’s a lot of ways we can continue to make scheduling easy in Slack, but we found that we could make the most impact by focusing on making it as easy as possible to share Calendly links in Slack, and more specifically Slack Connect, natively without the user having to disrupt their workflow.
We achieved this by focusing on 2 product features:
Richer link unfurling of calendly.com URLs in messages
/calendly global shortcut to easily share your Calendly event
In providing these two features, it was important that we keep the experience as simple and straightforward as possible for Slack users wanting to install and use the Calendly app. This meant that we needed to provide seamless authorization and then rich messaging to let users know what is going on.
When we thought about how we wanted to give our users’ the ability to use Calendly seamlessly in Slack, we need to consider how to handle data from Slack as well as the main Calendly application. More precisely, we need to capture events and interactions happening in Slack, as well as be able to make requests against the main Calendly application.
As a somewhat unique approach, we decided that the cleanest way to do this would be to build a standalone application that sits between Slack and Calendly, whose sole purpose is to handle events from Slack and make requests to Calendly.
We chose this layer of abstraction because it allows us to separate concerns of Slack from our main Calendly application, continue our approach of a service-oriented architecture, and dog food our own Calendly API.
When a user installs the Calendly app to their Slack workspace they actually authorize the use of the Calendly app for all users in that workspace. Slack provides this ability to determine the level of authorization granularity based on the token type your application designates, which in our case is the Bot User Token. For users, this means that they actually have access to the rich unfurling of Calendly links without actually authorizing their individual Calendly account. Only when they specifically want to use shortcuts to share their Calendly events directly in Slack do they need to authorize the use of their Calendly account.
Building a simple data model
We need a clean data model to represent this unique authorization mechanism: a single installation for an entire workspace associated with individual Slack users, who may authorize the use of their Calendly account. So we came up with a fairly simple model to represent this behavior:
When the Calendly app is installed to the workspace we store a
slack_installation record. This record holds the necessary access information like
bot_token to make authorized requests to Slack.
If a user authorizes the use of their Calendly account, we create a
slack_user record to associate that user to the
slack_installation and then create a
calendly_user record associated to that
calendly_user records hold the necessary information like access token to make authorized requests to Calendly. In this way we can continue to support installation across a workspace without requiring every user to authorize the Calendly App. We also get the benefit of allowing all users of a workspace to get the rich link unfurling without having to authorize their Calendly account if they choose not to.
Getting users authorized
Now that we covered the data model, let’s get into the flow of control and how we actually make it as easy as possible for users to get authorized:
When a user goes about installing the Calendly app to their workspace, we request authorization for access to specific scopes like
chat:write, link:write, shortcuts, user:read…
Upon authorization, Slack redirects to our Calendly app so we can persist their installation information. Rather than ask the user that installed the Calendly app to later authenticate with Calendly, the Calendly app immediately attempts to request the user to authorize access to their Calendly account. We do this by using the Calendly API to register an OAuth app that can provide authorization on behalf of Calendly users.
This part was a bit tricky but enabled a user that installed the Calendly app to be able to use all features immediately without any additional steps. In order to make this happen we actually pass the Slack
team.id and the
user.id encoded as a secure JWT through Calendly OAuth flow as a
state parameter. This way, when the user authorizes the Calendly app to access their Calendly account, we know specifically which Slack user to associate the Calendly user information with.
We also needed to consider the case when Slack users who installed the Calendly app to their workspace, but have not connected their Calendly account, know how and when they need to do so. Our solution was to have checks at each entry point of using the Calendly app to see if they still needed to connect their Calendly account and prompt them to do so if needed.
In order to do this we leveraged messages on attempting to use the shortcut,
and provide links to connect on the App Home page:
We feel that providing these messages at the time of use and making it as easy as possible to connect with Calendly, we can increase usage of the app as well as potentially new signups to Calendly.
Using Blocks, Slack Events API, and Interactivity
In order to support our two core features: rich link unfurling, and shortcuts to share your Calendly event, we heavily relied upon the Block Kit. Slack has created a highly customizable UI framework to allow app developers to display content in Slack.
We use these blocks throughout the Calendly app to display all messaging in Slack. We highly recommend testing and playing with the Block Kit Builder to understand how users will interact with them and how they can be combined together to achieve rich messages, modals, and more.
Slack Events API
Another nice feature of the Slack Developer ecosystem is the Slack Events API. In order to understand how it works, I’ll let the Slack API Docs explain: “Don’t call us, we’ll call you”. Slack’s API makes it easy to listen to specific events performed by the user. Once a user has installed the Calendly App to their workspace, the Calendly app listens for any
link_shared event that contains the “calendly.com” domain.
Upon receiving an event, we quickly reply with an acknowledgment of the event, make a request to the Calendly API to get more detailed information about the Calendly event shared, and respond back to the event with a
chat.unfurl request to the channel that the link was created in containing a set of blocks displaying a richer unfurling message that other Slack users can interact with.
Saving the best for last, a core feature and technical challenge we faced was allowing users to share their Calendly event without ever having to leave Slack. We decided that what made the most sense was to leverage Slack’s interactivity features including global shortcuts and the newly added modal surfaces. Here is an overview of how this works:
When a user selects the “Share an Event Type with Calendly” shortcut, the Calendly app receives an interactive shortcut payload that we can use to determine which user attempted to use the shortcut.
We then find the Calendly user associated with the Slack User and make a request to the Calendly API to get a list of the user’s Event Types. In the payload received from Slack, we receive what’s referred to as a
trigger_id, which allows us to respond to Slack with a
views.open request containing a modal view. Using the modal view object and blocks we are able to craft a modal that contains a variety of block elements.
In order to actually build the dropdown selection when choosing an Event Type, we used the
static_select input block element, and load each option as the event type received from the request from the Calendly API.
We also add a
conversations_select block element to the modal view. This allows the user to select where they want to post the message. In this element we set
response_url_enabled: true. This is important because it allows us to know where to post the scheduling message after the user submits the modal. We also use the checkbox block element to allow users to create a single use link and a multiline plain text input to allow users to post a message.
In order to provide an even better experience, we wanted to allow users to preview the event type details they would be sharing when they submitted the modal. This requires an update to the modal when the user selects an event type. In order to achieve this, we actually need to listen out for a block action payload, specifically when the event type is selected from the dropdown. We set an
action_id on the block and set up a listener for when that action is received for that specific
When we receive that action, we capture the existing event types in the options list and preserve it through the update so that the list of event types does not change. Slack preserves any selected views for an event type as long as the block elements remain. We then add a few additional blocks similar to the ones used for link unfurling so that users can preview the message they will create on modal submission. We then fire off a
views.update request using the same
view.id provided in the block action payload.
Finally, when a user clicks submit, Slack sends a
view_submission event. Like the other events, the Calendly app is listening for this specific event and captures this view_submission payload. We make a request to the Calendly API to get the chosen event type’s details or, if a user wants to use a single use link, we actually use that optional checkbox as our indicator to actually create a single use link in Calendly rather then just fetch the details of the Event Type. As mentioned earlier we reply back to the
response_url provided based on the
conversations_select block element we created when first rendering the modal.
And that’s how Slack users can seamlessly use Calendly without ever leaving Slack!
By combining existing functionality in Calendly with Slack’s customizable Block Kit, Events API, and interactive messaging, we as app developers are able to create seamless integrations to empower users to be more productive and make their work life easier.
By doing a little bit more work like automatically authorizing flows with all necessary parties or providing intuitive messaging with as much information as possible to guide the user, we can create apps in Slack that users love.