Answer
Slack is the one connector that writes: the Brew Slack app posts the cards and jams a team chooses to send, brings thread replies and Approve back into the jam, needs no Slack review, and is never evidence for a Play.
Today
The app
| Fact | Value | Source |
|---|---|---|
| App | Brew, A0C98G07J1M, in the joinsquareup workspace, built from docs/slack-app-manifest.json with the Slack CLI through tools/slack-app.mjs create or update |
memory brew-real-connectors |
| Keys | SLACK_CLIENT_ID, SLACK_CLIENT_SECRET, SLACK_SIGNING_SECRET in Secret Manager; the connector switches on when the first two are set |
brew/connectors/slack.js:5-6, 11 brew/docs/connectors-approvals.md:15 |
| Distribution | Public since 16 Oct: any workspace can install it; no review needed | memory brew-real-connectors brew/connectors/slack.js:5 |
| Slack Marketplace listing | Parked; Slack does not require it | brew/docs/connectors-approvals.md:36 |
| Bot scopes | chat:write, chat:write.public, incoming-webhook, channels:join, channels:read, groups:read, channels:history, groups:history, users:read, users:read.email, team:read |
brew/connectors/slack.js:12 brew/docs/slack-app-manifest.json |
| Bot events | message.channels, message.groups, app_uninstalled, tokens_revoked |
brew/docs/slack-app-manifest.json |
| Redirects | /connect/slack/callback on brew. and feed. |
brew/docs/slack-app-manifest.json |
| Manifest copy | Since 26 Oct says "marketing plays" and "We read a brand's own store"; the app name and the bot are still Brew, left as is until the name is picked. Not yet pushed to Slack in any source we read | brew/docs/slack-app-manifest.json:4-10 brew commit ca2bc7b |
| Messages | Slack refusals and replies say we and us, and the name in Invite @Brew comes from PRODUCT_NAME |
brew/connectors/slack.js:7, 32-34 brew/slack-routes.js:49-51 |
What happens
- The brand signs in with Continue with Slack and picks a channel on Slack's own screen; Brew joins that channel itself, because Slack only sends a bot replies from channels it is in brew/connectors/slack.js:1-3, 50-52.
- Share to Slack and Take it to Slack post for real, with an Approve button memory brew-real-connectors.
- Thread replies land in the jam and fold into the brief; what is written in the jam is mirrored to the thread memory brew-real-connectors.
- Each thread message is sorted by one cheap call: chatter folds into the brief; a change or any paid image or video becomes a proposal with Make it and Not now, run once on a yes; @Brew applies a text change at once memory brew-real-connectors brew/slack-routes.js:37-39.
- Approve moves the jam to Ready; the button must come from the workspace the brand connected, on the jam's own post brew/slack-routes.js:45-51.
- Slack's read is the workspace and channel only: no cards, no ledger rows brew/connectors/slack.js:3-4, 58-62.
Events and the relay
- Slack sends one app's events to one address:
feed.joinsquareup.com/slack/eventsand/slack/actionsbrew/docs/slack-app-manifest.json. - Every request is checked against the signing secret, at most 5 minutes old, and answered within Slack's 3 seconds brew/connectors/slack.js:23-29 brew/slack-routes.js:1-6.
- Production handles workspaces connected to its brands and relays any other to staging byte for byte, with the signature headers, so staging checks it the same way; staging never relays; a forged request is never relayed brew/slack-routes.js:19-26, 81, 90 brew/test/slack-relay.test.js brew/tools/prod-env.sh:9.
- Retried events are skipped for an hour by event id brew/slack-routes.js:29-30.
- A brand at home on one site gets no Slack from the other brew/docs/production.md:21.
Known limits
| Limit | Source |
|---|---|
| Approve can be pressed by anyone in the channel; Param said keep it | memory brew-go-live-2026-10-17 |
| A real Approve click in the Slack interface was checked only by a signed replay | memory brew-real-connectors |
A brand connected before channels:join existed needs /invite @Brew |
memory brew-real-connectors |
apps.manifest.update does not verify the events URL; press Retry on the app's Event Subscriptions page, then Save |
memory brew-real-connectors |
| The demo site stays simulated | memory brew-real-connectors |
Tomorrow
| Item | Version | Tag |
|---|---|---|
| Post only where the team sends it, replies only from Brew's own threads | v1 | Stays |
| Manifest and messages say plays, done on origin/main | v1 | Stays |
| App name and bot follow the chosen name | v1 | Changes |
| Approve limited to the brand's team members | v1 | Changes, if Param reverses his call |
| Weekly digest posted to the channel | v1 | Stays |
| Scheduled posts to Slack after a yes | v2 | New |
Rows that change v1 or add v2 are proposals for Param, not decisions.
How we do it
- Change the app only through the manifest, then push it:
node tools/slack-app.mjs update
memory brew-real-connectors
- After any events URL change, open the app's Event Subscriptions page, press Retry, then Save memory brew-real-connectors.
- Test on Cozy in a test channel; never post into a design partner's workspace to test memory brew-live-stores.
- When Slack refuses, show its plain words from
why(), such as invite @Brew to the channel brew/connectors/slack.js:31-34.
Numbers
| Metric | Definition | Where measured |
|---|---|---|
| Connected workspaces | Slack connections in state live | connections on production, Proposed |
| Replies into jams | Thread replies taken per week | Server log [slack] … a reply from, Proposed |
| Approvals from Slack | Approve presses that moved a jam to Ready | Server log, Proposed |
| Relayed events | Events production passed to staging | Server log [slack] relayed, Proposed |
Open
- List the app in the Slack Marketplace? Proposal: no until the name is picked; the listing would carry the old name.
- Who may press Approve? Param chose anyone in the channel on 17 Oct. Proposal: keep it, and record who pressed it in the jam as it does now.
- The manifest now says plays, and still names the app Brew. Proposal: push the new description with
tools/slack-app.mjs updatenow, and rename the app in one more update once the name is picked.