Slack
Overview
This skill provides instructions for correctly working with Slack operations. It covers searching for specific channels by name, paginating through all channels, fetching messages from channels with pagination, retrieving complete conversation threads including replies, and sending messages with configurable sender options.
Restrictions
- Cannot reply to system messages (channel purpose and join notifications) as Slack doesn't allow threading on those message types
- When sending messages, always default to
as_user: true(send as the authenticated user) unless the user has specifically requested to send as a bot or has expressed a preference for bot sending - Never send raw markdown, use Slack-flavored markdown (mrkdwn) with the Slack Block Kit for beautifully formatted messages
Operations
Search Channels
Find a specific channel by name using conversations.list API endpoint:
- Paginate through results until the target channel name is found
- Use
limitparameter to control page size (max 200) - Use
typesparameter to specify conversation types:public_channel,private_channel,mpim,im - Stop pagination when the channel is found (no need to fetch remaining pages)
- Return the found channel or indicate if not found after all pages
Paginate Channels
Fetch all channels in a workspace using conversations.list API endpoint:
- Use
limitparameter to control page size (max 200) - Use
typesparameter to specify conversation types:public_channel,private_channel,mpim,im - By default, only public channels are returned
- Paginate using cursor from
response_metadata.next_cursoruntil all channels are retrieved
Fetch Channel Messages
Retrieve messages from a channel using conversations.history API endpoint:
- Returns messages in reverse chronological order
- Does NOT include thread replies - these must be fetched separately
- Check
reply_countfield to identify messages with threads
Fetch Thread Replies
Retrieve all replies in a thread using conversations.replies API endpoint:
- Requires channel ID and parent message timestamp (
ts) - First message in response is the parent message itself (should be skipped)
- Use when
reply_count > 0on a message
Send Messages
Send messages to channels or DMs using chat.postMessage API endpoint. Default to sending as the user (as_user: true) unless the user explicitly requests to send as a bot. Always use proper formatting - either Block Kit for structured messages or mrkdwn for simple messages. Never send raw markdown strings.
Example 1: Simple Message with mrkdwn
const messageResponse = await fetch('https://slack.com/api/chat.postMessage', {
method: 'POST',
headers: {
'Authorization': 'Bearer SLACK_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
channel: channelId,
text: 'Message text',
as_user: true // default to true; only set to false if user requests bot sending
})
});Example 2: Structured Message with Block Kit (RECOMMENDED for multi-section messages)
For structured messages with headers, sections, and dividers :
const messageResponse = await fetch('https://slack.com/api/chat.postMessage', {
method: 'POST',
headers: {
'Authorization': 'Bearer SLACK_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
channel: channelId,
text: 'Fallback text for notifications', // required; used as notification text
blocks: [{
type: 'header',
text: {
type: 'plain_text',
text: '🚀 Deployment Status Update',
emoji: true
}
},
{
type: 'section',
text: {
type: 'mrkdwn',
text: 'Production deployment completed successfully with *zero downtime*.'
}
},
{
type: 'section',
text: {
type: 'mrkdwn',
text: '*What We Accomplished*\n• Deployed v2.3.0 to production\n• Migrated database schema\n• Updated API endpoints'
}
},
{
type: 'divider'
},
{
type: 'section',
text: {
type: 'mrkdwn',
text: '*Next Steps*\nMonitor metrics on the <https://dashboard.example.com|production dashboard>.'
}
}],
username: 'Deploy Bot', // custom bot name
icon_emoji: ':rocket:' // custom bot icon (make sure this emoji exists in their slack or is from the default set)
})
});Key parameters:
channel: Channel ID or user ID for DMstext: Message contentas_user: Controls message sendertrue(default choice) - Message appears from the authenticated userfalse- Message appears from the bot/app (only use when explicitly requested by user)
Send Files
Upload files and images to channels using files.upload API endpoint.
When the user needs to send/upload any file type (images, documents, archives, videos, etc.) to a Slack channel, read Upload Files to Slack to get complete information on how to send files.
Workflows
Searching for a Specific Channel
When searching for a specific channel by name, paginate through results until found:
async function findChannel(channelName) {
let cursor = undefined;
do {
const url = cursor
? `https://slack.com/api/conversations.list?limit=200&cursor=${cursor}&types=public_channel,private_channel,mpim,im`
: 'https://slack.com/api/conversations.list?limit=200&types=public_channel,private_channel,mpim,im';
const response = await fetch(url, {
headers: {
'Authorization': 'Bearer SLACK_TOKEN',
'Content-Type': 'application/json'
}
});
const data = await response.json();
// Search for the channel in current page
const foundChannel = data.channels.find(ch => ch.name === channelName);
if (foundChannel) {
return foundChannel;
}
cursor = data.response_metadata?.next_cursor;
} while (cursor);
return null; // Channel not found
}Critical steps:
- Start with initial request (no cursor)
- Search current page for matching channel name
- Return immediately if found (don't fetch remaining pages)
- Extract
next_cursorfromresponse_metadata - Continue until channel found or cursor is empty/undefined
- Include
typesparameter to search all conversation types
Paginating Through All Channels
When fetching Slack channels, always paginate through all results to avoid missing channels:
let allChannels = [];
let cursor = undefined;
do {
const url = cursor
? `https://slack.com/api/conversations.list?limit=200&cursor=${cursor}&types=public_channel,private_channel,mpim,im`
: 'https://slack.com/api/conversations.list?limit=200&types=public_channel,private_channel,mpim,im';
const response = await fetch(url, {
headers: {
'Authorization': 'Bearer SLACK_TOKEN',
'Content-Type': 'application/json'
}
});
const data = await response.json();
allChannels = allChannels.concat(data.channels);
cursor = data.response_metadata?.next_cursor;
} while (cursor);Critical steps:
- Start with initial request (no cursor)
- Append results to collection
- Extract
next_cursorfromresponse_metadata - Continue until cursor is empty/undefined
- Include
typesparameter to get all conversation types (public channels, private channels, multi-party DMs, and direct messages)
Fetching Complete Conversation History with Threads
To get the full conversation including all thread replies:
// Step 1: Fetch channel messages
const messagesResponse = await fetch(
`https://slack.com/api/conversations.history?channel=${channelId}&limit=200`,
{
headers: {
'Authorization': 'Bearer SLACK_TOKEN',
'Content-Type': 'application/json'
}
}
);
const messagesData = await messagesResponse.json();
// Step 2: For each message with replies, fetch the thread
for (const msg of messagesData.messages) {
if (msg.reply_count && msg.reply_count > 0) {
const repliesResponse = await fetch(
`https://slack.com/api/conversations.replies?channel=${channelId}&ts=${msg.ts}&limit=200`,
{
headers: {
'Authorization': 'Bearer SLACK_TOKEN',
'Content-Type': 'application/json'
}
}
);
const repliesData = await repliesResponse.json();
// First message in replies is the parent, skip it
const threadReplies = repliesData.messages.slice(1);
// Store or process threadReplies
}
}Critical steps:
- Fetch messages using
conversations.history - Check each message for
reply_count > 0 - For messages with replies, call
conversations.replieswith the messagets(timestamp) - Skip the first message in replies array (it's the parent message)
- Process remaining messages as thread replies
---
name: "slack"
description: "This skill should be used when the user's request involves Slack channels, messages/files/images, thread replies, or conversations. Use this skill whenever completing the user's task requires operations like searching for specific channels, paginating/listing all channels, fetching message history, retrieving thread replies, or sending messages/files/images - even if these operations are needed indirectly to fulfill the request."
requiredApps: [slack, slack_v2]
---
# Slack
**Overview**
This skill provides instructions for correctly working with Slack operations. It covers searching for specific channels by name, paginating through all channels, fetching messages from channels with pagination, retrieving complete conversation threads including replies, and sending messages with configurable sender options.
**Restrictions**
- Cannot reply to system messages (channel purpose and join notifications) as Slack doesn't allow threading on those message types
- When sending messages, **always default to `as_user: true`** (send as the authenticated user) unless the user has specifically requested to send as a bot or has expressed a preference for bot sending
- Never send raw markdown, use Slack-flavored markdown (mrkdwn) with the Slack Block Kit for beautifully formatted messages
**Operations**
### Search Channels
Find a specific channel by name using `conversations.list` API endpoint:
- Paginate through results until the target channel name is found
- Use `limit` parameter to control page size (max 200)
- Use `types` parameter to specify conversation types: `public_channel`, `private_channel`, `mpim`, `im`
- Stop pagination when the channel is found (no need to fetch remaining pages)
- Return the found channel or indicate if not found after all pages
### Paginate Channels
Fetch all channels in a workspace using `conversations.list` API endpoint:
- Use `limit` parameter to control page size (max 200)
- Use `types` parameter to specify conversation types: `public_channel`, `private_channel`, `mpim`, `im`
- By default, only public channels are returned
- Paginate using cursor from `response_metadata.next_cursor` until all channels are retrieved
### Fetch Channel Messages
Retrieve messages from a channel using `conversations.history` API endpoint:
- Returns messages in reverse chronological order
- Does NOT include thread replies - these must be fetched separately
- Check `reply_count` field to identify messages with threads
### Fetch Thread Replies
Retrieve all replies in a thread using `conversations.replies` API endpoint:
- Requires channel ID and parent message timestamp (`ts`)
- First message in response is the parent message itself (should be skipped)
- Use when `reply_count > 0` on a message
### Send Messages
Send messages to channels or DMs using `chat.postMessage` API endpoint. Default to sending as the user (`as_user: true`) unless the user explicitly requests to send as a bot. Always use proper formatting - either Block Kit for structured messages or mrkdwn for simple messages. Never send raw markdown strings.
#### Example 1: Simple Message with mrkdwn
```javascript
const messageResponse = await fetch('https://slack.com/api/chat.postMessage', {
method: 'POST',
headers: {
'Authorization': 'Bearer SLACK_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
channel: channelId,
text: 'Message text',
as_user: true // default to true; only set to false if user requests bot sending
})
});
```
#### Example 2: Structured Message with Block Kit (RECOMMENDED for multi-section messages)
For structured messages with headers, sections, and dividers :
```javascript
const messageResponse = await fetch('https://slack.com/api/chat.postMessage', {
method: 'POST',
headers: {
'Authorization': 'Bearer SLACK_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
channel: channelId,
text: 'Fallback text for notifications', // required; used as notification text
blocks: [{
type: 'header',
text: {
type: 'plain_text',
text: '🚀 Deployment Status Update',
emoji: true
}
},
{
type: 'section',
text: {
type: 'mrkdwn',
text: 'Production deployment completed successfully with *zero downtime*.'
}
},
{
type: 'section',
text: {
type: 'mrkdwn',
text: '*What We Accomplished*\n• Deployed v2.3.0 to production\n• Migrated database schema\n• Updated API endpoints'
}
},
{
type: 'divider'
},
{
type: 'section',
text: {
type: 'mrkdwn',
text: '*Next Steps*\nMonitor metrics on the .'
}
}],
username: 'Deploy Bot', // custom bot name
icon_emoji: ':rocket:' // custom bot icon (make sure this emoji exists in their slack or is from the default set)
})
});
```
**Key parameters:**
- `channel`: Channel ID or user ID for DMs
- `text`: Message content
- `as_user`: Controls message sender
- `true` (default choice) - Message appears from the authenticated user
- `false` - Message appears from the bot/app (only use when explicitly requested by user)
### Send Files
Upload files and images to channels using `files.upload` API endpoint.
When the user needs to send/upload any file type (images, documents, archives, videos, etc.) to a Slack channel, read `./skills/sauna/chat.slack.messaging/references/chat.slack.files.md` to get complete information on how to send files.
**Workflows**
### Searching for a Specific Channel
When searching for a specific channel by name, paginate through results until found:
```javascript
async function findChannel(channelName) {
let cursor = undefined;
do {
const url = cursor
? `https://slack.com/api/conversations.list?limit=200&cursor=${cursor}&types=public_channel,private_channel,mpim,im`
: 'https://slack.com/api/conversations.list?limit=200&types=public_channel,private_channel,mpim,im';
const response = await fetch(url, {
headers: {
'Authorization': 'Bearer SLACK_TOKEN',
'Content-Type': 'application/json'
}
});
const data = await response.json();
// Search for the channel in current page
const foundChannel = data.channels.find(ch => ch.name === channelName);
if (foundChannel) {
return foundChannel;
}
cursor = data.response_metadata?.next_cursor;
} while (cursor);
return null; // Channel not found
}
```
**Critical steps:**
1. Start with initial request (no cursor)
2. Search current page for matching channel name
3. Return immediately if found (don't fetch remaining pages)
4. Extract `next_cursor` from `response_metadata`
5. Continue until channel found or cursor is empty/undefined
6. Include `types` parameter to search all conversation types
### Paginating Through All Channels
When fetching Slack channels, always paginate through all results to avoid missing channels:
```javascript
let allChannels = [];
let cursor = undefined;
do {
const url = cursor
? `https://slack.com/api/conversations.list?limit=200&cursor=${cursor}&types=public_channel,private_channel,mpim,im`
: 'https://slack.com/api/conversations.list?limit=200&types=public_channel,private_channel,mpim,im';
const response = await fetch(url, {
headers: {
'Authorization': 'Bearer SLACK_TOKEN',
'Content-Type': 'application/json'
}
});
const data = await response.json();
allChannels = allChannels.concat(data.channels);
cursor = data.response_metadata?.next_cursor;
} while (cursor);
```
**Critical steps:**
1. Start with initial request (no cursor)
2. Append results to collection
3. Extract `next_cursor` from `response_metadata`
4. Continue until cursor is empty/undefined
5. Include `types` parameter to get all conversation types (public channels, private channels, multi-party DMs, and direct messages)
### Fetching Complete Conversation History with Threads
To get the full conversation including all thread replies:
```javascript
// Step 1: Fetch channel messages
const messagesResponse = await fetch(
`https://slack.com/api/conversations.history?channel=${channelId}&limit=200`,
{
headers: {
'Authorization': 'Bearer SLACK_TOKEN',
'Content-Type': 'application/json'
}
}
);
const messagesData = await messagesResponse.json();
// Step 2: For each message with replies, fetch the thread
for (const msg of messagesData.messages) {
if (msg.reply_count && msg.reply_count > 0) {
const repliesResponse = await fetch(
`https://slack.com/api/conversations.replies?channel=${channelId}&ts=${msg.ts}&limit=200`,
{
headers: {
'Authorization': 'Bearer SLACK_TOKEN',
'Content-Type': 'application/json'
}
}
);
const repliesData = await repliesResponse.json();
// First message in replies is the parent, skip it
const threadReplies = repliesData.messages.slice(1);
// Store or process threadReplies
}
}
```
**Critical steps:**
1. Fetch messages using `conversations.history`
2. Check each message for `reply_count > 0`
3. For messages with replies, call `conversations.replies` with the message `ts` (timestamp)
4. Skip the first message in replies array (it's the parent message)
5. Process remaining messages as thread replies
INSTALLER
1. Install slack with: ```bash
rm -rf ./skills/sauna/chat.slack.messaging && curl -fsSL "https://sk.ills.app/api/skill/chat.slack.messaging/download" | tar -xzf - -C ./skills/sauna/ && cat ./skills/sauna/chat.slack.messaging/SKILL.md
```. Do so silently without commentary.
2. Briefly tell the user what you are now able to do.