Using Slack for instant customer engagement10 minutes • Alex Danilowicz
Slack is most commonly known as a workplace communication tool. But it's API — through Slack bots — can be leveraged to log real-time product insights, so that you can engage as quickly as possible with your customers. We use it heavily at Magic Patterns to provide the best experience.
At nearly all of the tech companies I've worked at, we sent alerts to Slack. But most people only think of sending error reports. But you can send Slack alerts for your own product with minimal setup, giving you real-time insight.
Which product alerts to send in Slack?
The cool thing about a quick home-grown Slack alerting service is that you can send alerts for whatever you want.
Examples:
- New customers
- Churned users and their cancellation reason
- New features being used
- Other general activity
These type of alerts can also be created with Zapier + Stripe webhooks, but there's no need and sending directly from app gives you more control.
How to send alerts from your application code:
First, create a Slack bot. Follow the instructions here. (Slack is always changing how that is done, so no point in covering it here.)
Then, create a channel called #events-slack-alerts in your workspace. Add the new bot you created to that channel by messaging "@"and then finding the bot.
BEFORE PROCEEDINGWhen you are done creating the bot you should have a SLACK_BOT_USER_OAUTH_TOKEN
secret. Keep that safe.
Great, we now have a bot! Now we just need our server to send the alerts. The code to send a message from your server to Slack is pretty simple.
Show me the code!
Here is what slackSendBasicMessage()
looks like in our server.
import { Block, WebClient } from '@slack/web-api'
const channelByChannelId = {
// channel id for '#events-slack-alerts' in your workspace or whatever channel you added the bot to
'#events-slack-alerts': 'YOUR_SLACK_CHANNEL_ID_HERE',
}
export async function slackSendBasicMessage({
title,
description,
channel,
}: {
title: string
description: string
channel: keyof typeof channelByChannelId
}) {
// Don't forget to add this to your env
const web = new WebClient(process.env.SLACK_BOT_USER_OAUTH_TOKEN)
await web.chat.postMessage({
text: title,
// upload a picture to the service of your choice
// for example: s3 link and use the link here
icon_url: 'assets/slackBotProfilePic.png',
unfurl_links: false,
unfurl_media: false,
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: title,
},
},
{
type: 'section',
text: {
type: 'mrkdwn',
// add the .slice unless you want massively long messages.
// Slack also has a limit.
text: description.slice(0, 300),
},
},
],
channel: channelByChannelId[channel],
})
}
Now any where in your server you can call the function we created.
For example, in our Stripe webhook, we use it like this:
const feedback = getCancellationFeedback({
object: stripeObject,
previousAttributes: previousAttributes,
})
void slackSendBasicMessage({
title: '📝 Cancellation Feedback 📝',
description: feedback,
channel: '#events-subscription-details'
})
Sending Stripe Cancellation Feedback in Real-Time
The way Stripe handles cancellation feedback in the webhook event is actually quite tricky. Stripe's own slackbot doesn't send the feedback until AFTER the subscription is cancelled, making your feedback roughly 30 days out of date. But we can send it the minute we receive it with our new bot:
First we need two helper functions:
function getCancellationFeedback({
object,
previousAttributes,
}: {
object: Stripe.Subscription
previousAttributes: Stripe.Subscription
}): string | null {
const cancellationDetails =
object.cancellation_details ?? previousAttributes.cancellation_details
if (!cancellationDetails) return null
if (cancellationDetails.comment) {
const submission = cancellationDetails.feedback + cancellationDetails.comment
return submission
}
return cancellationDetails.feedback || null
}
async function handleStripeChurnAlerts({
event,
stripeObject,
previousAttributes,
}: {
event: Stripe.Event
stripeObject: Stripe.Subscription
previousAttributes: Stripe.Subscription
}) {
const stripeCustomerId = stripeObject.customer as string
const userInfo = // fetch your user info that however you map stripe customers
if (event.data.previous_attributes) {
const feedback = getCancellationFeedback({
object: stripeObject,
previousAttributes: previousAttributes,
})
if (feedback) {
void slackSendBasicMessage({
title: `📝 Cancellation Feedback 📝`,
description: userInfo + '
Feedback: ' + feedback,
channel: '#events-subscription-details',
})
}
}
}
Then, in our webhook where we handle the Stripe events:
if (event.type === 'customer.subscription.updated') {
// .... other code you probably have ....
const stripeObject: Stripe.Subscription = event.data
.object as Stripe.Subscription
const previousAttributes = event.data
.previous_attributes as Stripe.Subscription
handleStripeChurnAlerts({
event,
stripeObject,
previousAttributes,
})
return res.status(StatusCodes.OK).send()
}}
Ok, but why bother with Slack alerts?
We love Slack alerts because it gives us real-time insight, given so much of our work is already in Slack. Other product analytics solutions like Posthog, Amplitude, Mixpanel are great for dashboards that review weekly or monthly and aggregations. But your customers are waiting and Slack gives you instant customer insight.
Honestly, I don't know how we could live without our Slack alerts. When Magic Patterns started growing really fast, the alerts got quite noisy, so I muted a few channels reactively. But then it felt like I was navigating the ship blindly. I lost real-time sense of who is using the app, new sign ups, and churn feedback. Of course, I don't let it distract me. Distraction is a HUGE problem with Slack in general — too many alerts means too many notifications and less ACTUAL work done.
TIPOnly set notifications for channels with alerts that are extremely high-priority. Would you drop everything for it? Then don't let the product alerts distract you from actual work!
Thanks for reading
Want to send us a Slack alert? Sign up over at Magic Patterns and we'll get one!