
slack-bot
Send messages and rich content to Slack channels via webhooks or Bot API. Use Block Kit for formatted announcements, marketing reports, and community updates. Trigger phrases: "post to slack", "slack message", "slack webhook", "slack notification", "slack announcement", "send to slack", "slack marketing", "slack update", "slack channel".
- Installs
- 429
- Last 24h
- —
- Trending rank
- —
- Security
- Not audited
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/openclaudia/openclaudia-skills --skill slack-bot- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads slack-bot automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
SKILL.md
The instructions your agent reads when the skill is triggered.
Slack Bot
Send messages and rich content to Slack channels using Incoming Webhooks or the Slack Web API. Build formatted announcements, marketing reports, metrics dashboards, and community updates with Block Kit.
Prerequisites
Requires either SLACK_WEBHOOK_URL or SLACK_BOT_TOKEN set in .env, .env.local, or
~/.claude/.env.global.
source ~/.claude/.env.global 2>/dev/null
source .env 2>/dev/null
source .env.local 2>/dev/null
if [ -n "$SLACK_WEBHOOK_URL" ]; then
echo "SLACK_WEBHOOK_URL is set. Webhook mode available."
elif [ -n "$SLACK_BOT_TOKEN" ]; then
echo "SLACK_BOT_TOKEN is set. Web API mode available."
else
echo "Neither SLACK_WEBHOOK_URL nor SLACK_BOT_TOKEN is set."
echo "See the Setup Guide below to configure Slack credentials."
fi
If neither variable is set, instruct the user to follow the Setup Guide section below.
Setup Guide
Option A: Incoming Webhook (Simple)
Incoming Webhooks are the fastest way to post messages. They require no OAuth scopes and are scoped to a single channel.
- Go to https://api.slack.com/apps and click Create New App > From scratch.
- Name the app (e.g., "Marketing Bot") and select your workspace.
- In the left sidebar, click Incoming Webhooks and toggle it On.
- Click Add New Webhook to Workspace at the bottom.
- Select the channel to post to and click Allow.
- Copy the Webhook URL (starts with
https://hooks.slack.com/services/...). - Add it to your environment:
echo 'SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T.../B.../xxxx' >> .env
Limitations: One webhook per channel. Cannot read messages, list channels, or reply to
threads programmatically (you must know the thread_ts from a prior API response).
Option B: Bot Token (Full Featured)
Bot tokens give access to the full Slack Web API: post to any channel the bot is in, reply to threads, list channels, upload files, and more.
- Go to https://api.slack.com/apps and click Create New App > From scratch.
- Name the app and select your workspace.
- In the left sidebar, click OAuth & Permissions.
- Under Bot Token Scopes, add these scopes:
chat:write- Post messageschat:write.public- Post to channels without joiningchannels:read- List public channelsfiles:write- Upload files (optional, for images/reports)reactions:write- Add emoji reactions (optional)
- Click Install to Workspace at the top and authorize.
- Copy the Bot User OAuth Token (starts with
xoxb-). - Add it to your environment:
echo 'SLACK_BOT_TOKEN=xoxb-your-token-here' >> .env
- Invite the bot to the channels it should post in: type
/invite @YourBotNamein each channel.
Optional: Set a default channel for convenience:
echo 'SLACK_DEFAULT_CHANNEL=#marketing' >> .env
Method 1: Incoming Webhooks
Send a Simple Text Message
curl -s -X POST "$SLACK_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d '{
"text": "Hello from the marketing bot!"
}'
Send a Message with Username and Icon Override
curl -s -X POST "$SLACK_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d '{
"text": "New blog post published!",
"username": "Marketing Bot",
"icon_emoji": ":mega:"
}'
Send a Message with Block Kit (Webhook)
curl -s -X POST "$SLACK_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d '{
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "New Product Launch"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Product X* is now live! Check out the announcement."
}
},
{
"type": "divider"
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Read the full announcement on our blog."
},
"accessory": {
"type": "button",
"text": {
"type": "plain_text",
"text": "Read More"
},
"url": "https://example.com/blog/launch"
}
}
]
}'
Method 2: Slack Web API (Bot Token)
The Web API provides full control over message delivery, threading, channel management, and more.
API Base
All requests go to https://slack.com/api/ with the header Authorization: Bearer {SLACK_BOT_TOKEN}.
Post a Message to a Channel
curl -s -X POST "https://slack.com/api/chat.postMessage" \
-H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"channel": "#marketing",
"text": "Weekly metrics report is ready!",
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "Weekly Marketing Metrics"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Here are the numbers for this week."
}
}
]
}'
The response includes a ts (timestamp) field which identifies the message. Save this value
for threading replies:
# Post and capture the message timestamp for threading
RESPONSE=$(curl -s -X POST "https://slack.com/api/chat.postMessage" \
-H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"channel": "#marketing",
"text": "Thread parent message"
}')
MESSAGE_TS=$(echo "$RESPONSE" | python3 -c "import json,sys; print(json.load(sys.stdin).get('ts',''))")
echo "Message timestamp: $MESSAGE_TS"
Reply to a Thread
Use the thread_ts parameter to reply inside an existing thread:
curl -s -X POST "https://slack.com/api/chat.postMessage" \
-H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"channel\": \"#marketing\",
\"thread_ts\": \"${MESSAGE_TS}\",
\"text\": \"This is a threaded reply with additional details.\"
}"
To also broadcast the reply to the channel (so it appears in the main conversation as well),
add "reply_broadcast": true.
Update an Existing Message
curl -s -X POST "https://slack.com/api/chat.update" \
-H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"channel\": \"#marketing\",
\"ts\": \"${MESSAGE_TS}\",
\"text\": \"Updated message content.\",
\"blocks\": []
}"
Delete a Message
curl -s -X POST "https://slack.com/api/chat.delete" \
-H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"channel\": \"#marketing\",
\"ts\": \"${MESSAGE_TS}\"
}"
List Public Channels
Useful for discovering which channel to post to:
curl -s "https://slack.com/api/conversations.list?types=public_channel&limit=100" \
-H "Authorization: Bearer ${SLACK_BOT_TOKEN}" | \
python3 -c "
import json, sys
data = json.load(sys.stdin)
for ch in data.get('channels', []):
members = ch.get('num_members', 0)
print(f\"#{ch['name']} | Members: {members} | ID: {ch['id']}\")
"
Upload a File to a Channel
curl -s -X POST "https://slack.com/api/files.uploadV2" \
-H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \
-F "file=@report.pdf" \
-F "filename=weekly-report.pdf" \
-F "channel_id=C0123456789" \
-F "initial_comment=Here is this week's marketing report."
Add an Emoji Reaction
curl -s -X POST "https://slack.com/api/reactions.add" \
-H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"channel\": \"C0123456789\",
\"timestamp\": \"${MESSAGE_TS}\",
\"name\": \"white_check_mark\"
}"
Slack Block Kit Reference
Block Kit is Slack's UI framework for building rich, interactive messages. Messages are composed of an array of blocks, each with a specific type and structure.
Block Types
| Block Type | Purpose | Supports mrkdwn |
|---|---|---|
header | Large bold title text | No (plain_text only) |
section | Primary content block with text and optional accessory | Yes |
divider | Horizontal line separator | N/A |
image | Full-width image with alt text | N/A |
context | Small, muted text and images (for metadata, timestamps) | Yes |
actions | Row of interactive elements (buttons, selects, date pickers) | N/A |
rich_text | Advanced formatted text (lists, quotes, code blocks) | N/A |
Header Block
{
"type": "header",
"text": {
"type": "plain_text",
"text": "Weekly Marketing Report",
"emoji": true
}
}
Section Block
Plain text section:
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Traffic is up 23%* this week compared to last week.\nOrganic search drove most of the growth."
}
}
Section with fields (two-column layout):
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": "*Visitors*\n12,450"
},
{
"type": "mrkdwn",
"text": "*Signups*\n342"
},
{
"type": "mrkdwn",
"text": "*MRR*\n$28,500"
},
{
"type": "mrkdwn",
"text": "*Churn*\n1.2%"
}
]
}
Section with a button accessory:
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "New blog post: *How We Grew 10x in 6 Months*"
},
"accessory": {
"type": "button",
"text": {
"type": "plain_text",
"text": "Read Post"
},
"url": "https://example.com/blog/growth-story",
"action_id": "read_blog_post"
}
}
Section with an image accessory:
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*New Feature: Dark Mode*\nOur most requested feature is finally here."
},
"accessory": {
"type": "image",
"image_url": "https://example.com/images/dark-mode-preview.png",
"alt_text": "Dark mode preview"
}
}
Divider Block
{
"type": "divider"
}
Image Block
{
"type": "image",
"image_url": "https://example.com/images/chart.png",
"alt_text": "Weekly traffic chart",
"title": {
"type": "plain_text",
"text": "Traffic Overview"
}
}
Context Block
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": "Posted by *Marketing Team* | Feb 10, 2026"
},
{
"type": "image",
"image_url": "https://example.com/logo-small.png",
"alt_text": "Company logo"
}
]
}
Actions Block (Buttons)
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Approve"
},
"style": "primary",
"action_id": "approve_action",
"value": "approved"
},
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Reject"
},
"style": "danger",
"action_id": "reject_action",
"value": "rejected"
},
{
"type": "button",
"text": {
"type": "plain_text",
"text": "View Details"
},
"url": "https://example.com/details",
"action_id": "view_details"
}
]
}
Button styles: "primary" (green), "danger" (red), or omit for default (gray).
Note on interactivity: Buttons with action_id (no url) require a Request URL configured
in your Slack App settings under Interactivity & Shortcuts to receive the button click
payload. Buttons with a url field open the link directly and do not require a backend.
Block Kit Limits
| Limit | Value |
|---|---|
| Blocks per message | 50 |
| Characters per text block | 3,000 |
| Characters per header | 150 |
| Fields per section | 10 |
| Elements per actions block | 25 |
| Elements per context block | 10 |
Block Kit Builder
Use the visual builder to design and preview messages before coding them: https://app.slack.com/block-kit-builder
Slack mrkdwn Formatting Guide
Slack uses its own markdown variant called mrkdwn. It differs from standard Markdown in several ways.
| Formatting | Syntax | Example |
|---|---|---|
| Bold | *text* | bold text |
| Italic | _text_ | italic text |
| Strikethrough | ~text~ | |
| Code (inline) | `text` | inline code |
| Code block | ```text``` | Multi-line code block |
| Blockquote | >text | Quoted text |
| Link | <https://url|display text> | Clickable link |
| User mention | <@U0123456> | @username |
| Channel mention | <#C0123456> | #channel |
| Emoji | :emoji_name: | :rocket: |
| Bulleted list | Start line with - or * | Bullet point |
| Numbered list | Start line with 1. | Numbered item |
| Line break | \n in JSON string | New line |
Important differences from standard Markdown:
- Bold uses single asterisks
*bold*, not double**bold**. - Italic uses underscores
_italic_, not single asterisks. - Links use
<url|text>format with a pipe, not[text](url). - Headers do not exist in mrkdwn. Use the
headerblock type instead. - Images cannot be inlined in mrkdwn. Use the
imageblock oraccessoryinstead.
Message Templates
Template 1: Product Announcement
curl -s -X POST "$SLACK_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d '{
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": ":rocket: New Feature: [Feature Name]",
"emoji": true
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "We just shipped *[Feature Name]* — here is what it does and why it matters.\n\n:point_right: *What it does:* [One-sentence description]\n:point_right: *Why it matters:* [Key benefit for users]\n:point_right: *How to try it:* [Quick instructions or link]"
}
},
{
"type": "image",
"image_url": "https://example.com/feature-screenshot.png",
"alt_text": "Feature screenshot"
},
{
"type": "divider"
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": ":newspaper: Read Announcement",
"emoji": true
},
"url": "https://example.com/blog/feature-launch",
"action_id": "read_announcement"
},
{
"type": "button",
"text": {
"type": "plain_text",
"text": ":play_or_pause_button: Watch Demo",
"emoji": true
},
"url": "https://example.com/demo",
"action_id": "watch_demo"
}
]
},
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": "Posted by *Product Team* | :speech_balloon: Reply in thread with questions"
}
]
}
]
}'
Template 2: Weekly Metrics Report
curl -s -X POST "$SLACK_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d '{
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": ":bar_chart: Weekly Marketing Metrics — Feb 3-9, 2026",
"emoji": true
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Here is this week'\''s performance snapshot."
}
},
{
"type": "divider"
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": ":globe_with_meridians: *Website Traffic*"
},
"fields": [
{
"type": "mrkdwn",
"text": "*Sessions*\n45,230 (:arrow_up: 12%)"
},
{
"type": "mrkdwn",
"text": "*Unique Visitors*\n31,870 (:arrow_up: 8%)"
},
{
"type": "mrkdwn",
"text": "*Bounce Rate*\n42.3% (:arrow_down: 2.1%)"
},
{
"type": "mrkdwn",
"text": "*Avg. Session Duration*\n3m 42s (:arrow_up: 15s)"
}
]
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": ":money_with_wings: *Conversion Metrics*"
},
"fields": [
{
"type": "mrkdwn",
"text": "*Signups*\n487 (:arrow_up: 18%)"
},
{
"type": "mrkdwn",
"text": "*Trial-to-Paid*\n12.4% (:arrow_up: 1.2%)"
},
{
"type": "mrkdwn",
"text": "*MRR*\n$52,300 (:arrow_up: $3,200)"
},
{
"type": "mrkdwn",
"text": "*Churn*\n1.8% (:arrow_down: 0.3%)"
}
]
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": ":email: *Email Performance*"
},
"fields": [
{
"type": "mrkdwn",
"text": "*Emails Sent*\n12,400"
},
{
"type": "mrkdwn",
"text": "*Open Rate*\n34.2%"
},
{
"type": "mrkdwn",
"text": "*Click Rate*\n4.8%"
},
{
"type": "mrkdwn",
"text": "*Unsubscribes*\n23"
}
]
},
{
"type": "divider"
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*:bulb: Key Takeaways*\n- Organic traffic up 15% after publishing 3 new blog posts\n- Email welcome sequence A/B test: Variant B outperformed by 22%\n- Trial signup spike on Thursday correlated with Product Hunt feature"
}
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Full Dashboard",
"emoji": true
},
"url": "https://analytics.example.com/dashboard",
"action_id": "view_dashboard"
}
]
},
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": "Auto-generated by Marketing Bot | Data from Google Analytics + Stripe"
}
]
}
]
}'
Template 3: Blog Post Share
curl -s -X POST "$SLACK_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d '{
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": ":pencil: New Blog Post Published",
"emoji": true
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*<https://example.com/blog/post-slug|Blog Post Title Here>*\n\nA brief summary of what the post covers — keep it to 2-3 sentences that capture the key value and make people want to click through."
},
"accessory": {
"type": "image",
"image_url": "https://example.com/blog/post-og-image.png",
"alt_text": "Blog post cover image"
}
},
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": ":bust_in_silhouette: Author: *Jane Smith* | :clock1: 6 min read | :label: SEO, Growth"
}
]
},
{
"type": "divider"
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": ":mega: *Help us amplify!* Share this post on your socials. Here are ready-to-use snippets:\n\n*Twitter/X:* _Just published: [title]. [Key insight from the post]. Link in reply._\n\n*LinkedIn:* _We just published a deep dive on [topic]. Here is the #1 takeaway: [insight]._"
}
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Read the Post",
"emoji": true
},
"style": "primary",
"url": "https://example.com/blog/post-slug",
"action_id": "read_post"
},
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Share on Twitter",
"emoji": true
},
"url": "https://twitter.com/intent/tweet?text=Check%20out%20this%20post&url=https://example.com/blog/post-slug",
"action_id": "share_twitter"
},
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Share on LinkedIn",
"emoji": true
},
"url": "https://www.linkedin.com/sharing/share-offsite/?url=https://example.com/blog/post-slug",
"action_id": "share_linkedin"
}
]
}
]
}'
Template 4: Team Update / Standup
curl -s -X POST "$SLACK_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d '{
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": ":clipboard: Marketing Team Update — Monday, Feb 10",
"emoji": true
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*:white_check_mark: Completed Last Week*\n- Launched email welcome sequence v2\n- Published 3 blog posts (SEO, product, case study)\n- Set up Google Ads remarketing campaign\n- Shipped landing page A/B test (Variant B live)"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*:construction: In Progress*\n- Content calendar for March (70% done)\n- Competitor analysis report (due Wednesday)\n- Social media campaign for Product Hunt launch"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*:dart: This Week'\''s Priorities*\n1. Finalize Product Hunt launch assets\n2. Send weekly newsletter (Thursday 9am)\n3. Review and approve Q1 ad spend budget\n4. Onboard new content writer"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*:warning: Blockers*\n- Waiting on design team for Product Hunt gallery images\n- Need legal review on new case study before publishing"
}
},
{
"type": "divider"
},
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": ":speech_balloon: Reply in thread with your own updates or questions"
}
]
}
]
}'
Template 5: Incident / Urgent Notification
curl -s -X POST "$SLACK_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d '{
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": ":rotating_light: Marketing Alert",
"emoji": true
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Issue:* [Brief description of the problem]\n*Impact:* [Who/what is affected]\n*Status:* :red_circle: Active\n*Owner:* <@U0123456>"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Details:*\n[Longer explanation. What happened, when it started, what we know so far.]"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Next Steps:*\n1. [Action item 1]\n2. [Action item 2]\n3. [Action item 3]"
}
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Status Page"
},
"url": "https://status.example.com",
"action_id": "status_page"
}
]
}
]
}'
Workflows
Workflow 1: Post a Marketing Announcement
When the user asks to post an announcement, product update, or news to Slack:
- Gather details - Ask for the announcement title, body, link, image URL, and target channel.
- Choose a template - Select from the templates above or build a custom Block Kit payload.
- Build the payload - Construct the JSON with proper mrkdwn formatting.
- Preview - Show the user the full JSON payload and describe how it will render.
- Confirm - Ask the user to approve before sending.
- Send - Execute the curl command.
- Report - Show the API response. For Web API, capture the
tsfor threading.
Workflow 2: Post Weekly Metrics
When the user asks to send a metrics report or dashboard to Slack:
- Collect metrics - Ask for the numbers or help pull them from analytics tools.
- Format with fields - Use section blocks with
fieldsfor the two-column metric layout. - Add context - Include week-over-week comparisons with arrow emoji.
- Add takeaways - Summarize 2-3 key insights in a section block.
- Include a dashboard link - Add a button to the full analytics dashboard.
- Send and thread - Post the main report, then thread detailed breakdowns as replies.
Workflow 3: Share a Blog Post
When a new blog post needs to be distributed to the team:
- Get the URL - Ask for the blog post URL, or fetch the latest from the blog.
- Extract metadata - Use WebFetch to pull the title, description, author, and OG image.
- Build the share message - Use Template 3 with social amplification snippets.
- Add share buttons - Include Twitter and LinkedIn intent URLs pre-populated with the post.
- Post to the channel - Send to the team marketing channel.
Workflow 4: Community Engagement
For managing Slack community channels (public communities, customer channels):
- Welcome messages - Post a welcome message with rules and resources when new members join.
- Scheduled updates - Post weekly roundups of popular discussions or new resources.
- Event announcements - Share upcoming webinars, AMAs, or meetups with RSVP buttons.
- Polls and feedback - Use actions blocks with buttons to collect quick feedback.
- Thread management - Reply to existing threads with updates or answers.
Sending Messages with Dynamic Content
Build Payloads with Shell Variables
TITLE="Product X v2.0 Released"
DESCRIPTION="Version 2.0 includes dark mode, API improvements, and 3x faster performance."
LINK="https://example.com/changelog/v2"
IMAGE_URL="https://example.com/images/v2-banner.png"
CHANNEL="#announcements"
curl -s -X POST "https://slack.com/api/chat.postMessage" \
-H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \
-H "Content-Type: application/json" \
-d "$(python3 -c "
import json
payload = {
'channel': '${CHANNEL}',
'text': '${TITLE}',
'blocks': [
{
'type': 'header',
'text': {'type': 'plain_text', 'text': '${TITLE}', 'emoji': True}
},
{
'type': 'section',
'text': {'type': 'mrkdwn', 'text': '${DESCRIPTION}'}
},
{
'type': 'image',
'image_url': '${IMAGE_URL}',
'alt_text': '${TITLE}'
},
{
'type': 'actions',
'elements': [{
'type': 'button',
'text': {'type': 'plain_text', 'text': 'Learn More'},
'url': '${LINK}',
'style': 'primary',
'action_id': 'learn_more'
}]
}
]
}
print(json.dumps(payload))
")"
Build Payloads from a JSON File
For complex messages, write the payload to a file first:
# Write the payload
cat > /tmp/slack-message.json << 'PAYLOAD'
{
"channel": "#marketing",
"text": "Fallback text for notifications",
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "Message Title"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Message body with *bold* and _italic_ formatting."
}
}
]
}
PAYLOAD
# Send it
curl -s -X POST "https://slack.com/api/chat.postMessage" \
-H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \
-H "Content-Type: application/json" \
-d @/tmp/slack-message.json
Scheduled Messages
Post a message at a specific future time using chat.scheduleMessage:
# Schedule a message for a specific Unix timestamp
# Use: date -d "2026-02-12 09:00:00" +%s (Linux) or date -j -f "%Y-%m-%d %H:%M:%S" "2026-02-12 09:00:00" +%s (macOS)
SEND_AT=$(date -j -f "%Y-%m-%d %H:%M:%S" "2026-02-12 09:00:00" +%s 2>/dev/null || date -d "2026-02-12 09:00:00" +%s)
curl -s -X POST "https://slack.com/api/chat.scheduleMessage" \
-H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"channel\": \"#marketing\",
\"post_at\": ${SEND_AT},
\"text\": \"Good morning team! Here is today's marketing agenda.\",
\"blocks\": []
}"
List scheduled messages:
curl -s "https://slack.com/api/chat.scheduledMessages.list" \
-H "Authorization: Bearer ${SLACK_BOT_TOKEN}" | \
python3 -c "
import json, sys, datetime
data = json.load(sys.stdin)
for msg in data.get('scheduled_messages', []):
ts = datetime.datetime.fromtimestamp(msg['post_at']).strftime('%Y-%m-%d %H:%M')
print(f\"ID: {msg['id']} | Channel: {msg['channel_id']} | Scheduled: {ts}\")
"
Delete a scheduled message:
curl -s -X POST "https://slack.com/api/chat.deleteScheduledMessage" \
-H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"channel": "C0123456789",
"scheduled_message_id": "Q0123456789"
}'
Multi-Channel Posting
Post the same message to multiple channels:
CHANNELS=("#marketing" "#general" "#product")
MESSAGE='{"text":"Big announcement coming tomorrow!","blocks":[{"type":"section","text":{"type":"mrkdwn","text":":mega: *Big announcement coming tomorrow!* Stay tuned."}}]}'
for CHANNEL in "${CHANNELS[@]}"; do
echo "Posting to ${CHANNEL}..."
echo "$MESSAGE" | python3 -c "
import json, sys
msg = json.load(sys.stdin)
msg['channel'] = '${CHANNEL}'
print(json.dumps(msg))
" | curl -s -X POST "https://slack.com/api/chat.postMessage" \
-H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \
-H "Content-Type: application/json" \
-d @- | python3 -c "
import json, sys
r = json.load(sys.stdin)
if r.get('ok'):
print(f' Sent. ts={r[\"ts\"]}')
else:
print(f' Error: {r.get(\"error\", \"unknown\")}')
"
done
Error Handling
Common API Errors
| Error | Cause | Fix |
|---|---|---|
invalid_auth | Bad or expired token | Regenerate the bot token in Slack App settings |
channel_not_found | Bot not in channel or wrong channel name | Invite bot with /invite @BotName or use channel ID |
not_in_channel | Bot needs to join the channel first | Invite the bot or use chat:write.public scope |
too_many_attachments | Over 50 blocks | Split the message into multiple posts or thread replies |
msg_too_long | Text exceeds 40,000 characters | Shorten the message or split into parts |
rate_limited | Too many requests | Wait the number of seconds in the Retry-After header |
missing_scope | Token lacks required permission | Add the scope in OAuth & Permissions and reinstall the app |
Validate a Response
RESPONSE=$(curl -s -X POST "https://slack.com/api/chat.postMessage" \
-H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"channel":"#marketing","text":"Test message"}')
python3 -c "
import json, sys
r = json.loads('${RESPONSE}'.replace(\"'\", \"\"))
if r.get('ok'):
print(f'Message sent successfully. ts={r[\"ts\"]} channel={r[\"channel\"]}')
else:
print(f'Error: {r.get(\"error\", \"unknown\")}')
if r.get('response_metadata', {}).get('messages'):
for m in r['response_metadata']['messages']:
print(f' Detail: {m}')
" 2>/dev/null || echo "$RESPONSE"
A more robust approach using a temp file:
RESPONSE_FILE=$(mktemp)
curl -s -X POST "https://slack.com/api/chat.postMessage" \
-H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"channel":"#marketing","text":"Test message"}' \
-o "$RESPONSE_FILE"
python3 -c "
import json
with open('${RESPONSE_FILE}') as f:
r = json.load(f)
if r.get('ok'):
print(f'Sent. ts={r[\"ts\"]}')
else:
print(f'Error: {r.get(\"error\")}')
"
rm -f "$RESPONSE_FILE"
Tips
- Always include a
textfield alongsideblocks— it serves as the fallback for notifications, accessibility readers, and clients that do not support Block Kit. - Use the Block Kit Builder at https://app.slack.com/block-kit-builder to visually design and preview messages before building the curl commands.
- For production workflows, use
chat.postMessage(Web API) over webhooks. It returns a messagetsyou can use for threading, updating, and deleting. - Thread long reports. Post a summary as the parent message and details as threaded replies to keep channels clean.
- Use
:emoji:codes inplain_textfields with"emoji": trueto render emoji in headers and button labels. - Escape special characters in mrkdwn:
&becomes&,<becomes<,>becomes>. - Rate limits: Slack allows roughly 1 message per second per channel. For bulk posting, add a 1-second delay between requests.
- When posting metrics, use section
fieldsfor the two-column layout rather than trying to format tables in mrkdwn (Slack does not support tables). - Always show the user the full message payload and ask for confirmation before posting.
FAQ
What does the slack-bot skill do?
Send messages and rich content to Slack channels via webhooks or Bot API. Use Block Kit for formatted announcements, marketing reports, and community updates. Trigger phrases: "post to slack", "slack message", "slack webhook", "slack notification", "slack announcement", "send to slack", "slack marketing", "slack update", "slack channel".
How do I install slack-bot?
Run npx skills add https://github.com/openclaudia/openclaudia-skills --skill slack-bot in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/slack-bot/ (personal) or .claude/skills/slack-bot/ (project).
Which AI agents can use slack-bot?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is slack-bot safe to install?
No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.
Who maintains slack-bot?
It is published by openclaudia in openclaudia/openclaudia-skills. It has 429 recorded installs.
More from openclaudia/openclaudia-skills
feishu-lark
openclaudia/openclaudia-skills
Send messages and interactive cards to Feishu (飞书) and Lark channels via webhooks or Bot API. Create rich-text announcements, marketing updates, and team notifications. Trigger phrases: "post to feishu", "feishu message", "lark message", "feishu webhook", "lark webhook", "send to feishu", "send to lark", "feishu bot", "lark bot", "飞书", "飞书机器人".
facebook-ads
openclaudia/openclaudia-skills
Create Facebook and Meta ad campaigns, write ad copy, define audiences, and plan budgets. Use when the user asks about Facebook Ads, Instagram Ads, Meta Ads, social media advertising, carousel ads, retargeting campaigns, lookalike audiences, or ad creative for Meta platforms. Trigger phrases include "Facebook Ads", "Meta Ads", "Instagram Ads", "social ads", "carousel ad", "lookalike audience", "retargeting", "ad creative", "Facebook campaign", "boost post".
video-ad-analysis
openclaudia/openclaudia-skills
Deconstruct and analyze video ad creatives for marketing insights. Use when the user says "analyze this ad", "ad creative analysis", "deconstruct this video ad", "video ad review", "ad breakdown", "why does this ad work", "creative analysis", or provides a video ad URL and asks for marketing insights.
content-calendar
openclaudia/openclaudia-skills
Plan and schedule social media and content marketing calendars. Monthly and weekly planning, content mix ratios, theme days, platform-specific timing, and batch creation workflows. Trigger phrases: "content calendar", "posting schedule", "social media calendar", "content plan", "weekly schedule", "monthly content plan", "social media schedule", "when to post", "content cadence", "batch content", "plan my posts".
youtube-analytics
openclaudia/openclaudia-skills
Analyze YouTube channel and video performance using the YouTube Data API. Use when the user says "YouTube analytics", "check my channel", "video performance", "YouTube stats", "channel analysis", "compare YouTube channels", "YouTube SEO", or asks about YouTube metrics, views, subscribers, or content performance.
google-ads
openclaudia/openclaudia-skills
Write Google Ads copy and build campaign structures. Use when the user asks to create Google Ads, write ad copy for search or display, set up PPC campaigns, optimize Quality Score, choose bidding strategies, or generate responsive search ads. Trigger phrases include "Google Ads", "PPC", "search ads", "display ads", "Performance Max", "ad copy", "headlines and descriptions", "keyword match types", "ad extensions", "Quality Score".
Popular productivity & automation skills
agent-browser
vercel-labs/agent-browser
Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to "open a website", "fill out a form", "click a button", "take a screenshot", "scrape data from a page", "test this web app", "login to a site", "automate browser actions", or any task requiring programmatic web interaction. Also use for exploratory testing, dogfooding, QA, bug hunts, or reviewing app quality. Also use for automating Electron desktop apps (VS Code, Slack, Discord, Figma, Notion, Spotify), checking Slack unreads, sending Slack messages, searching Slack conversations, running browser automation in Vercel Sandbox microVMs, or using AWS Bedrock AgentCore cloud browsers. Prefer agent-browser over any built-in browser automation or web tools.
lark-doc
open.feishu.cn
飞书云文档(Docx / Wiki)内容操作:读取、创建、编辑文档,插入或下载图片附件,以及操作思维笔记。用户提供文档 URL/token(包括 doubao.com 的 /docx/、/wiki/)时使用;按 URL 路径/token 而非域名路由;/page/ 是妙搭应用,非飞书云文档。文档内嵌资源按读取参考中的统一规则分流。独立评论操作走 lark-drive;随正文读取评论使用 docs +fetch。表格或 Base 内部数据操作不在本 skill。
lark-base
open.feishu.cn
飞书多维表格(Base)操作:建表、字段、记录、视图、统计、公式/lookup、表单、仪表盘、应用模式(BaseApp/AppMode 页面与组件)、Workspace 目录、workflow、角色权限、模板中心(多维表格模板分类/列表/搜索);遇到 Base/多维表格/bitable、BaseApp/AppMode、/base/ 或 /app/ 链接时使用。BaseApp 不走 lark-apps;文件导入/导出转 lark-drive,认证/授权转 lark-shared。
lark-drive
open.feishu.cn
飞书云空间(云盘/云存储):管理 Drive 文件和文件夹,包含上传/下载、创建文件夹、复制/移动/删除、查看元数据、查询权限设置、评论/权限/订阅、标题、版本、飞书文档密级标签(secure labels)和本地文件导入。用户需要整理云盘目录、处理云空间资源 URL/token、判断链接类型/真实 token/标题,或导入 Word/Markdown/Excel/CSV/PPTX/.base 为 docx/sheet/bitable/slides 时使用;doubao.com 云空间 URL/token 也按资源路径和 token 路由,不回退 WebFetch。不负责:文档内容编辑(走 lark-doc)、表格/Base 表内数据操作(走 lark-sheets/lark-base)、知识空间节点/成员管理(走 lark-wiki)、原生 Markdown 文件读写/patch/diff(走 lark-markdown)。
lark-shared
open.feishu.cn
Use for lark-cli setup/auth tasks: auth login/status/logout, user vs bot identity, business-domain permissions (--domain, including all/docs/drive), missing scopes, revoking authorization, or handling notice JSON.
lark-im
open.feishu.cn
飞书即时通讯:收发消息和管理群聊。发送和回复消息、搜索聊天记录、管理群聊成员、上传下载图片和文件、管理表情回复、发送应用内/短信/电话加急、发送和处理交互卡片(Interactive Card)、监听卡片按钮回调(card.action.trigger)。当用户需要发消息、查看或搜索聊天记录、下载聊天中的文件、查看群成员、搜索群、创建群聊或话题群、管理标记数据、管理 Feed 置顶(添加/移除/查询置顶会话)、管理标签数据、处理卡片回调时使用。