Skip to main content
Give your product a Slack presence. Your customers install your Slack app into their own workspaces, their @mentions and DMs arrive as messages in your Dairo mailbox, and your agent replies with the same send call it uses for email. Dairo carries the messages; every reply is written by your code.

How it works

Slack follows the same receive-and-reply model as Telegram, with an OAuth install flow instead of a pasted bot token:
  1. Register a Slack app for your account — Dairo can mint one for you, or you import one you already own.
  2. Mint an install URL and put it behind the “Add to Slack” button in your product.
  3. A customer approves the install; their workspace binds to your account as a channel: "slack" inbox.
  4. @mentions of the bot and DMs to it land in your mailbox and fire the message.received webhook; your agent replies with POST /v1/messages.
There is no built-in responder anywhere in this path. Dairo stores what arrives and delivers what you send — nothing goes out until your code calls the send endpoint, and a send that names no conversation is rejected rather than guessed.

Register your Slack app

POST /v1/slack/apps registers the app whose name and icon your customers see, in one of two ways:
  • Managed — pass origin: "managed" and a Slack app-configuration token from your Slack app dashboard. Dairo creates the app for you and wires up its event delivery. The token is used once and never stored.
  • Bring your own — pass origin: "byo" with an existing app’s appId, clientId, clientSecret, and signingSecret. The secrets are encrypted at rest and never returned by any read.
The response is the app’s public identity — never its secrets. An app already registered to a different Dairo account is refused with a 409. If you prefer to create the app in Slack’s UI yourself, GET /v1/slack/apps/manifest-template returns a manifest to paste into Slack’s “Create app from manifest” flow; import the resulting credentials with origin: "byo" afterward.

Ambient channel reading

contextMode on the create call decides what the app listens to:
  • "mentions" (default) — @mentions of the bot and DMs to it, nothing else.
  • "ambient" — additionally subscribes to the channel message stream.
An ambient app carries an ambientMode, settable with PATCH /v1/slack/apps/{appId} (its only mutable field): @mentions and DMs always notify as usual; ambientMode only governs the non-mention channel stream. Setting "store" on a "mentions" app is a 400.

Mint the install URL

POST /v1/slack/oauth/start returns a signed https://slack.com/oauth/v2/authorize URL. Embed it as your “Add to Slack” button — the signed state inside it ties the resulting workspace binding to your account.
oauth/start resolves your account’s single Slack app. With zero or more than one app registered it returns a 400 naming the explicit form, POST /v1/slack/apps/{appId}/install-url, which mints the same URL for a specific app.
The state in the URL is signed, single-use, and expires after 10 minutes. Mint a fresh URL per click rather than caching one — a stale link fails the install.

What an install creates

When a customer approves the install, their workspace binds to your account as one channel: "slack" inbox. From that moment the workspace’s traffic to your bot is yours to receive and reply to.
  • One workspace, one inbox per app. The same owner reinstalling reconnects the existing inbox in place — fresh credentials, same inbox id.
  • A workspace already bound to a different Dairo account through the same app is refused; a binding belongs to exactly one account.
  • If the workspace later removes the app, the inbox is marked disconnected. Sends to it return a definitive 422 (slack_disconnected) until the app is reinstalled — never a retryable error, because no retry can succeed.

Receive @mentions and DMs

Every @mention of the bot and every DM to it lands in the mailbox as a message with channel: "slack" and fires the message.received webhook — the same event email and Telegram fire, so your agent reacts instead of polling. Webhook payloads are metadata-first: the event carries the messageId, and you fetch the full message for the Slack coordinates. GET /v1/messages/{messageId} returns:
An @mention, fetched by id and trimmed
Everything your agent needs to reply is in channelMetadata: Slack ids are case-sensitive. Store and echo them back exactly as received — a lowercased id targets the wrong conversation, or none.
Branch on channelType before answering. "dm" is a private 1:1 — account-specific details are fine. "channel" and "private_channel" are rooms where other people are watching: do not disclose account data, balances, or anything you would not post publicly. Answer briefly, or offer to continue in a DM. Dairo gives you the fact; the judgment is your agent’s.

Reply

Reply with the same POST /v1/messages call you use on every channel, pointed at the Slack inbox. One Slack-specific rule: you must address the conversation explicitly. A Slack inbox is a whole workspace of conversations, so there is deliberately no “reply to whoever spoke last” default — that would race one user’s answer into another user’s DM. Put one of these targets in to: To keep a channel reply attached to the question, echo back slack:{channelId}:{threadTs ?? ts} from the inbound message’s channelMetadata.
The response comes back with status: "sent" and channel: "slack", and the send shows up in GET /v1/messages like any other outbound message. Reply errors are definitive, never guesses:
  • No slack: target in to400, slack_target_required.
  • slack:{userId} for a user who has never messaged the bot — 422; address the channelId explicitly instead.
  • The workspace uninstalled the app — 422, slack_disconnected.

From webhook to reply

The whole loop: a message.received event arrives, your agent decides what to say, and the reply lands in the right conversation with the right privacy. Signature verification is elided here — wire it up as in Build a webhook receiver.
Node.js

Behavior and limits

  • Text only, for now. Attachments and scheduled sends (sendAt) return a 400 on the Slack channel. An html body is flattened to plain text — Slack renders its own formatting, not HTML.
  • No notification injection. Relayed content cannot trigger a <!channel>-style mass notification: Slack control sequences in the body are neutralized at delivery and render as literal text.
  • No bot loops. Messages authored by bots — including your bot’s own replies echoing back — are never ingested, so two bots cannot ping-pong.
  • Threads map to threads. Replies inside a Slack thread share one Dairo threadId, so a conversation reads as one thread in the mailbox.
  • Scopes. Reading rides messages:read and sending rides messages:send. Everything under /v1/slack/* — apps, manifest template, install URLs — rides inboxes:write, the same scope as creating an inbox.
  • Quota. Each Slack send counts as one message against your monthly message quota; a send over quota returns a 429 before anything posts.