Inspiration
Account managers live in two places. Slack, where the real conversation with a customer actually happens, and a CRM, where none of it gets recorded until someone remembers to write it down. By the time anyone opens the CRM to check in, the customer has often already decided to leave. I wanted to close that gap: build something that reads the conversation as it happens, inside the tool people are already using, and tells a human the moment a relationship is genuinely at risk. Not another dashboard to check. Not another tool to adopt. Something that lives where the conversation already lives.
What it does
Pulse watches Slack Connect channels shared between an account team and their customer, and turns everyday conversation into an early warning system for churn risk.
- Pattern detection scans every message for risk signals across seven categories: contract concerns, competitor mentions, frustration, deprioritization, formality shifts, data exports, and internal discussion leaks.
- Health scoring combines message frequency, response latency, risk flags, champion silence, and external signals into a single 0 to 100 score:
$$ \text{Score} = \max\Big(0,\ \min\Big(100,\ 100 - \sum_{i=1}^{5} P_i\Big)\Big) $$
where each $P_i$ is the penalty from one of the five factors above. On top of that, a compound risk rule escalates urgency by one full tier, but only when a severe pattern flag, a critically silent champion, and a severe external signal are all present at once. A single offhand comment never triggers an escalation on its own.
- Champion tracking automatically identifies the customer's internal champion from conversation patterns and flags when they've gone quiet, independent of whether anyone else on their team is still talking.
- Salesforce MCP integration. Pulse is a genuine MCP client. It spawns Salesforce's own official CLI MCP server and pulls live CRM data straight into every brief. When an alert fires, it writes a Task back to the Salesforce Account, so the CRM becomes a real record of what Pulse detected instead of a static lookup nobody updates.
- Claude generated briefs synthesize the conversation, health trend, champion status, external signals, and CRM data into a plain English summary with a suggested next action.
- Human in the loop alerts land as Block Kit cards DM'd to the account manager, who reviews and edits a suggested outreach message in a compose modal before anything goes out. Pulse never messages the customer on its own.
@Pulse askanswers questions about an account using Slack's Real Time Search API over that account's own conversation history, with no separate search tool needed.
How I built it
Pulse is a Python Slack Bolt app running in Socket Mode, backed by SQLite. Every account facing feature (champion tracking, external signals, Salesforce CRM) is opt in per account and off by default.
The Salesforce piece is where MCP does real work. Pulse spawns Salesforce's official CLI MCP server, @salesforce/mcp, as a subprocess over stdio, and speaks the actual Model Context Protocol to it using the official mcp Python SDK: a stdio_client transport wrapping a ClientSession, initialized and then called with run_soql_query to pull live Account data (industry, revenue, headcount) straight out of Salesforce. That data flows directly into the brief Claude writes. Authentication is headless, through the Salesforce JWT Bearer Flow with a self signed certificate, so the whole thing runs on a remote server with no browser or interactive login involved. When an alert fires, Pulse writes a Task back to that same Salesforce Account, so the CRM ends up with a real record of what Pulse detected instead of a static lookup nobody ever updates.
The other required piece, Real Time Search, powers @Pulse ask. It calls Slack's own assistant.search.context endpoint using the short lived action_token issued on the triggering event, so an account manager can ask a plain English question and get an answer grounded in that specific account's actual Slack Connect history, not a general web search.
External signal scanning runs as a separate daily job, scheduled with APScheduler outside the request cycle entirely. For each opted in account, it pulls that company's Google News RSS feed and hands the results to Claude, which classifies each headline for relevance and severity, rejecting false positives like a different company that happens to share the same name. Anything that clears the bar becomes a severity scored signal that feeds directly into the health score, and can factor into the compound risk escalation alongside the pattern flags and champion silence.
For hosting, I moved off a laptop dependent setup onto AWS EC2 with systemd managing the process, so Pulse stays live and responsive for the full multi week judging window instead of depending on a development machine staying awake.
Challenges I ran into
- The hosted MCP connector wasn't working for this use case. Anthropic's server side MCP connector kept failing authorization against Salesforce's hosted MCP server, a known issue tracked in public GitHub discussions at the time. Rather than wait on an upstream fix, I switched to self hosting Salesforce's own official MCP server directly, which turned out to be the more robust path anyway: full control over the process, real visibility into failures, and no dependency on a third party's hosted infrastructure being available.
- Right sizing the host. My first EC2 instance (
t3.micro, 1GB RAM) couldn't reliably run the Salesforce MCP subprocess. The first npm install alone (700+ packages) pushed it into severe swap thrashing even with a 2GB swap file added. I diagnosed it properly instead of papering over it, and moved tot3.small, which resolved the issue cleanly and was verified in production. - A live version pin break. Mid testing, an unpinned
npx @salesforce/mcpsilently picked up a newer release that required a Node API my host's Node version didn't have, breaking every Salesforce call with no change on my end. I pinned to the last known good version and rebuilt with reproducibility in mind. - A real null handling bug, found live: Salesforce accounts with an empty
AnnualRevenuefield crashed brief generation, because.get(key, default)only applies when a key is missing, not when it's present butnull. Fixed by handlingNoneexplicitly instead of trusting the dictionary default. - A concurrency bug. Several Slack messages arriving within seconds of each other could each pass the alert throttle check before the first one finished writing, causing duplicate Claude and Salesforce calls for one logical event. Fixed with a per account lock around alert dispatch.
Accomplishments that I'm proud of
Pulse isn't a demo running against synthetic data. Every account is registered on a real Slack Connect channel spanning two real workspaces, scored against real conversation history, with a real Salesforce org on the other end of the MCP connection. The compound risk escalation, the champion silence tracking, the Salesforce write back: all of it has fired for real, not just in a controlled test. It also satisfies two of the challenge's three qualifying technologies at once, MCP server integration and Real Time Search, stacked together rather than used in isolation.
What I learned
That the honest, live version of a demo is more convincing than a polished, staged one. Watching a health score actually move in response to a real message, in real time, says more than any pre baked number ever could. And that a self hosted MCP integration, while more work up front than a hosted connector, gives me a level of control and debuggability that pays for itself the first time something breaks in a way I didn't expect.
What's next for Pulse
- Move Salesforce write back fully onto MCP once the official server ships a write tool, retiring the current REST fallback.
- Generalize beyond Salesforce to other CRMs as they publish MCP servers of their own.
- Sync Slack's
message_changedandmessage_deletedevents so historical scoring reflects edits and retractions, not just the original message. - A rolled up, team wide health view across every monitored account, inside Slack itself.
Built With
- anthropic-claude
- apscheduler
- aws-ec2
- google-news-rss
- node.js
- npm
- python
- salesforce
- salesforce-cli
- salesforce-jwt-bearer-flow
- salesforce-mcp-server
- slack-api
- slack-block-kit
- slack-bolt
- slack-connect
- slack-real-time-search-api
- socket-mode
- soql
- sqlite
- systemd
Log in or sign up for Devpost to join the conversation.