PRODUTO

Smart Slack Notifications: Route CRM Events to the Right Channel

Andres Muguira17 de fevereiro de 20266 min de leitura
SlackNotificationsIntegracaoAutomacao
← Back to Blog
Resumir com IA

Beyond Basic Slack Integration

Most CRM-Slack integrations are lazy. They connect to one channel - usually #general or a dedicated #sales channel - and dump every notification there. New contact created? #sales. Deal updated? #sales. Someone logged a call note? #sales. Within a week, the channel becomes a wall of noise that everyone mutes. The integration exists on paper, but nobody actually reads the notifications. We have seen this pattern with HubSpot's Slack integration, Copper's integration, and half a dozen other CRM tools we evaluated before building our own.

When we built SalesSheet's Slack integration, we started from a different premise: notifications are only useful if they reach the right people in the right channel. A new enterprise lead should ping #enterprise-sales. A closed deal should celebrate in #wins. A logged call note might not need a Slack notification at all. The channel picker and granular event controls are what make this possible.

The best Slack integration is the one your team does not mute. That means sending the right events to the right channels - not everything to one place.
Channel picker with searchable list of public and private channels

Building the Channel Picker

The channel picker is the first thing you see after connecting your Slack workspace via OAuth. It is a searchable dropdown that lists every channel your SalesSheet Slack bot has access to - public channels prefixed with #, private channels with a lock icon. The implementation uses Slack's conversations.list API endpoint, which returns channels, private channels, and direct messages depending on the OAuth scopes you request.

We request three scopes during the OAuth flow: channels:read for listing public channels, groups:read for listing private channels the bot has been invited to, and chat:write for sending messages. We intentionally do not request channels:join because we want the workspace admin to control which channels the bot can access. If you want SalesSheet notifications in a private channel, you invite the bot to that channel first, then it appears in the picker. This respects existing workspace permissions rather than overriding them.

The API call itself is straightforward, but the pagination handling is where most integrations fall short. Slack's conversations.list endpoint returns a maximum of 200 channels per request. For large workspaces with hundreds or thousands of channels, you need to follow the response_metadata.next_cursor field to fetch subsequent pages. We handle this by making recursive API calls on the server side (inside a Supabase Edge Function) and returning the complete, flattened channel list to the client. The client-side picker does not know or care about pagination - it receives the full list and provides instant search filtering.

Handling Large Workspaces

During testing, we connected to a workspace with over 800 channels. The initial implementation made the API calls sequentially and took nearly 4 seconds to load the channel list. That is unacceptable for a settings dropdown. We optimized this in two ways.

First, we cache the channel list in Supabase with a 5-minute TTL. When you open the channel picker, it loads the cached list instantly and fetches a fresh list in the background. If the fresh list differs (new channels added, old ones archived), the picker updates without a page reload. Second, we implemented the search as a client-side filter on the cached list rather than making new API calls per keystroke. Typing "ent" immediately filters to #enterprise, #enterprise-sales, #entertainment - no network roundtrip required.

The caching strategy also helps with Slack's rate limits. The conversations.list endpoint is rate-limited to roughly 20 requests per minute per workspace. If multiple team members open the Slack settings page simultaneously, they all hit the cache instead of each triggering their own cascade of paginated API calls. This is especially important during initial setup when everyone on the team is configuring their preferences.

Granular Event Control

10 notification toggles for different CRM event types

Below the channel picker, ten individual toggle switches let you control exactly which CRM events trigger Slack notifications. The events are organized into three groups: Contact events (created, updated, deleted), Opportunity events (created, updated, deleted, stage changed), and Activity events (call logged, meeting logged, email sent, note added). Each toggle is independent - you can enable "Opportunity Stage Changed" while disabling "Opportunity Updated" to only get notified when deals move through your sales pipeline, not when someone edits a deal's description.

A master toggle at the top enables or disables all notifications at once. This is useful for temporarily silencing notifications during a data import or cleanup session - you do not want 500 "Contact Created" notifications flooding Slack while importing a CSV. Toggle everything off, run the import, toggle back on.

The notification messages themselves are formatted using Slack's Block Kit, which gives us rich message layouts with contact names as bold text, deal values formatted as currency, and direct links back to the relevant record in SalesSheet. Clicking the contact name in a Slack notification opens that contact's detail page in app.salessheets.ai. The link includes the record ID and navigates directly, no extra clicks required.

Direct Messages from the AI Chat

AI chat sending a Slack DM to a teammate

Channel notifications are the passive side of our Slack integration. The active side is sending messages directly from the AI assistant. Type "DM Maria on Slack about the Acme deal closing" in the AI chat, and the assistant composes a contextual message, finds Maria's Slack user ID via the users.list API, opens a DM channel via conversations.open, and posts the message. The entire flow happens in one natural language command.

The user lookup is smart about name matching. If you say "DM Maria," the system searches across display names, real names, and usernames. If there are multiple Marias in your workspace, the AI asks which one you mean. If you say "DM maria.garcia@company.com," it does an exact email match using Slack's users.lookupByEmail endpoint, which is faster and unambiguous.

The DM capability required an additional OAuth scope: im:write for opening direct message channels and posting to them. We also request users:read and users:read.email for the user lookup functionality. All of these scopes are requested during the initial OAuth connection so there is no secondary authorization prompt when you first try to send a DM.

The AI sending Slack DMs is one of those features that sounds simple but changes workflows profoundly. You never leave the CRM to coordinate with your team.

Real-Time Delivery and Error Handling

Slack message delivery is handled through a Supabase Edge Function called slack-notify. When a CRM event fires (via Supabase's database webhook triggers), the function checks the user's notification preferences, formats the message, and posts it to the configured channel. The entire pipeline - database event to Slack message - takes under 500 milliseconds in production.

Error handling covers three main failure modes. First, the bot might be removed from a private channel after the user configured it as their notification destination. The Slack API returns a channel_not_found error, and we surface this in the SalesSheet settings page with a clear message: "Bot was removed from #enterprise-sales. Re-invite the bot or choose a different channel." Second, the workspace admin might revoke the bot's token entirely. We detect this via 401 responses from the Slack API and prompt the user to reconnect. Third, Slack might be down. We implement a simple retry with exponential backoff - three attempts over 60 seconds before giving up and logging the failure for later inspection.

We also handle the case where a user disconnects their Slack workspace from SalesSheet. All notification preferences and cached channel lists are deleted from our database. If they reconnect later, they start with a clean configuration. We do not preserve stale preferences because channel IDs can change - a channel might be archived and recreated with the same name but a different ID. Starting fresh is safer than delivering notifications to the wrong place.

What We Learned Building This

Three lessons from building the Slack integration. First, cache aggressively. Every Slack API call has rate limits, and the limits are lower than you expect for non-Enterprise Grid workspaces. Second, design the UI for the 90% case. Most users have 10-50 channels and will pick their notification channel in the first 3 seconds. The search bar and pagination handling serve the 10% with large workspaces but should never slow down the common case. Third, test with real workspaces. Our development Slack workspace had 12 channels. The first time we connected to a customer's workspace with 400+ channels, three bugs appeared that our testing never caught - pagination cursor handling, search performance on long lists, and a UI overflow when channel names exceeded 40 characters.

The Slack integration is one of SalesSheet's most-used features, and the channel picker is why. When notifications go to the right place, people actually read them. That is the entire point of connecting your CRM to your team's communication tool - not just proving the integration exists, but making it genuinely useful.

Try SalesSheet Free

No credit card required. Start selling smarter today.

Start Free Trial