the setup
The Apollo Dashboard cockpit already had email in it — Gmail triaged by AI, replied to inline, all from one screen.
I wanted Slack in the same place.
Not a new Slack app. Not a new OAuth flow. On the Arcade auth (my MCP auth provider) we already had — which was holding live Slack tokens for all three workspaces: an ecommerce business (my supply-chain business), my consulting practice, and a cancer education business.
The only question was: what can those tokens actually do?
the wall
First thing I did was read the config.
config/businesses.yaml. base_client's DEFAULT_SCOPES. authorize_slack.py. Three different files, three different scope lists — none of them showed search:read or chat:write.
I read those files and told myself the inverse of the truth: no global mention search, inline reply not available.
That was completely wrong.
the fix
One script: ingest/slack_probe.py. One job: hit the Slack API and read the X-OAuth-Scopes response header back.
What came back across all three workspaces:
channels:read,channels:history,groups:history,im:history,mpim:historysearch:read← THERE it ischat:write← and THAT
Wait — chat:write was there the whole time? Yes. The configs were stale. They reflected what we asked for at setup, not what Arcade had actually negotiated and granted.
That meant search.messages could find @mentions globally — not channel by channel. an ecommerce business: 2,217 matches. a cancer education business: 2,123. my consulting practice: 5 (the quiet one).
building v12
Once we knew what we actually had, the build was fast.
- New
/slackpage: DMs + global@mentionsearch + 15 watched channels (8 an ecommerce business / 4 my consulting practice / 3 the cancer education business), triaged byglm-5:cloud(action / fyi / noise) - High-signal items merge into existing surfaces:
/emailfor "Needs action,"/attentionfor fyi, tagged 💬 - Inline reply shells out to
ingest/slack_send_cli.py— and the reply target is derived server-side from the stored row (row_id=workspace:channel:ts), so a "send immediately" can't mis-fire to the wrong channel - Own 15-minute
launchdjob:com.apollo.dashboard-slack-ingest.plist
Eleven commits.
v13: threading the context
Email had one thing Slack doesn't: GmailClient.get_thread(). One call, full thread.
Slack has no equivalent. No get_thread. No get_replies. You call conversations.history and then conversations.replies raw, stitch the results, and cache them yourself.
So that's what v13 did — 90-day context cache stored locally, and a WYSIWYG composer sitting right below every thread. Same pattern as the Superhuman email reader we'd just shipped. Three rounds of Q&A, an advisor review, six build phases, three commits (2d7495c, e7ecd11, 454ce82).
Every watched thread now loads with full context. The composer is right there.
why this matters to me
I build a lot of things across three businesses and I'm in Slack constantly — but I was still alt-tabbing out of the cockpit every time something needed a reply.
That's gone now. And the scope discovery reminded me of something I keep relearning: OAuth configs drift silently. You declare the scopes once, the provider grants what it grants, time passes, nobody updates the YAML. The live X-OAuth-Scopes header is the truth. The config is the rumor.
Probe before you build. The platform probably gave you more than you think.