Slack bot skill

Send messages and rich content to Slack channels via webhooks or Bot API.

by OpenClaudia·MIT license·★ 705 Stars on the repo·GitHub ↗

Use now

Files of Slack bot

OpenClaudia/main1 file shown
SKILL.md
Show the full text1247 lines

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.

  1. Go to https://api.slack.com/apps and click Create New App > From scratch.
  2. Name the app (e.g., "Marketing Bot") and select your workspace.
  3. In the left sidebar, click Incoming Webhooks and toggle it On.
  4. Click Add New Webhook to Workspace at the bottom.
  5. Select the channel to post to and click Allow.
  6. Copy the Webhook URL (starts with https://hooks.slack.com/services/...).
  7. 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).

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.

  1. Go to https://api.slack.com/apps and click Create New App > From scratch.
  2. Name the app and select your workspace.
  3. In the left sidebar, click OAuth & Permissions.
  4. Under Bot Token Scopes, add these scopes:
    • chat:write - Post messages
    • chat:write.public - Post to channels without joining
    • channels:read - List public channels
    • files:write - Upload files (optional, for images/reports)
    • reactions:write - Add emoji reactions (optional)
  5. Click Install to Workspace at the top and authorize.
  6. Copy the Bot User OAuth Token (starts with xoxb-).
  7. Add it to your environment:
echo 'SLACK_BOT_TOKEN=xoxb-your-token-here' >> .env
  1. Invite the bot to the channels it should post in: type /invite @YourBotName in 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 "[email protected]" \
  -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~ strikethrough
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 header block type instead.
  • Images cannot be inlined in mrkdwn. Use the image block or accessory instead.

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:

  1. Gather details - Ask for the announcement title, body, link, image URL, and target channel.
  2. Choose a template - Select from the templates above or build a custom Block Kit payload.
  3. Build the payload - Construct the JSON with proper mrkdwn formatting.
  4. Preview - Show the user the full JSON payload and describe how it will render.
  5. Confirm - Ask the user to approve before sending.
  6. Send - Execute the curl command.
  7. Report - Show the API response. For Web API, capture the ts for threading.
Workflow 2: Post Weekly Metrics

When the user asks to send a metrics report or dashboard to Slack:

  1. Collect metrics - Ask for the numbers or help pull them from analytics tools.
  2. Format with fields - Use section blocks with fields for the two-column metric layout.
  3. Add context - Include week-over-week comparisons with arrow emoji.
  4. Add takeaways - Summarize 2-3 key insights in a section block.
  5. Include a dashboard link - Add a button to the full analytics dashboard.
  6. 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:

  1. Get the URL - Ask for the blog post URL, or fetch the latest from the blog.
  2. Extract metadata - Use WebFetch to pull the title, description, author, and OG image.
  3. Build the share message - Use Template 3 with social amplification snippets.
  4. Add share buttons - Include Twitter and LinkedIn intent URLs pre-populated with the post.
  5. Post to the channel - Send to the team marketing channel.
Workflow 4: Community Engagement

For managing Slack community channels (public communities, customer channels):

  1. Welcome messages - Post a welcome message with rules and resources when new members join.
  2. Scheduled updates - Post weekly roundups of popular discussions or new resources.
  3. Event announcements - Share upcoming webinars, AMAs, or meetups with RSVP buttons.
  4. Polls and feedback - Use actions blocks with buttons to collect quick feedback.
  5. 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 text field alongside blocks — 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 message ts you 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 in plain_text fields with "emoji": true to render emoji in headers and button labels.
  • Escape special characters in mrkdwn: & becomes &amp;, < becomes &lt;, > becomes &gt;.
  • 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 fields for 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.
1---
2name: slack-bot
3description: >
4 Send messages and rich content to Slack channels via webhooks or Bot API. Use Block Kit for
5 formatted announcements, marketing reports, and community updates. Trigger phrases:
6 "post to slack", "slack message", "slack webhook", "slack notification", "slack announcement",
7 "send to slack", "slack marketing", "slack update", "slack channel".
8allowed-tools:
9 - Bash
10 - WebFetch
11 - WebSearch
12---
13 
14# Slack Bot
15 
16Send messages and rich content to Slack channels using Incoming Webhooks or the Slack Web API.
17Build formatted announcements, marketing reports, metrics dashboards, and community updates
18with Block Kit.
19 
20## Prerequisites
21 
22Requires either `SLACK_WEBHOOK_URL` or `SLACK_BOT_TOKEN` set in `.env`, `.env.local`, or
23`~/.claude/.env.global`.
24 
25```bash
26source ~/.claude/.env.global 2>/dev/null
27source .env 2>/dev/null
28source .env.local 2>/dev/null
29 
30if [ -n "$SLACK_WEBHOOK_URL" ]; then
31 echo "SLACK_WEBHOOK_URL is set. Webhook mode available."
32elif [ -n "$SLACK_BOT_TOKEN" ]; then
33 echo "SLACK_BOT_TOKEN is set. Web API mode available."
34else
35 echo "Neither SLACK_WEBHOOK_URL nor SLACK_BOT_TOKEN is set."
36 echo "See the Setup Guide below to configure Slack credentials."
37fi
38```
39 
40If neither variable is set, instruct the user to follow the Setup Guide section below.
41 
42---
43 
44## Setup Guide
45 
46### Option A: Incoming Webhook (Simple)
47 
48Incoming Webhooks are the fastest way to post messages. They require no OAuth scopes and are
49scoped to a single channel.
50 
511. Go to https://api.slack.com/apps and click **Create New App** > **From scratch**.
522. Name the app (e.g., "Marketing Bot") and select your workspace.
533. In the left sidebar, click **Incoming Webhooks** and toggle it **On**.
544. Click **Add New Webhook to Workspace** at the bottom.
555. Select the channel to post to and click **Allow**.
566. Copy the Webhook URL (starts with `https://hooks.slack.com/services/...`).
577. Add it to your environment:
58 
59```bash
60echo 'SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T.../B.../xxxx' >> .env
61```
62 
63**Limitations:** One webhook per channel. Cannot read messages, list channels, or reply to
64threads programmatically (you must know the `thread_ts` from a prior API response).
65 
66### Option B: Bot Token (Full Featured)
67 
68Bot tokens give access to the full Slack Web API: post to any channel the bot is in, reply to
69threads, list channels, upload files, and more.
70 
711. Go to https://api.slack.com/apps and click **Create New App** > **From scratch**.
722. Name the app and select your workspace.
733. In the left sidebar, click **OAuth & Permissions**.
744. Under **Bot Token Scopes**, add these scopes:
75 - `chat:write` - Post messages
76 - `chat:write.public` - Post to channels without joining
77 - `channels:read` - List public channels
78 - `files:write` - Upload files (optional, for images/reports)
79 - `reactions:write` - Add emoji reactions (optional)
805. Click **Install to Workspace** at the top and authorize.
816. Copy the **Bot User OAuth Token** (starts with `xoxb-`).
827. Add it to your environment:
83 
84```bash
85echo 'SLACK_BOT_TOKEN=xoxb-your-token-here' >> .env
86```
87 
888. Invite the bot to the channels it should post in: type `/invite @YourBotName` in each channel.
89 
90**Optional:** Set a default channel for convenience:
91 
92```bash
93echo 'SLACK_DEFAULT_CHANNEL=#marketing' >> .env
94```
95 
96---
97 
98## Method 1: Incoming Webhooks
99 
100### Send a Simple Text Message
101 
102```bash
103curl -s -X POST "$SLACK_WEBHOOK_URL" \
104 -H "Content-Type: application/json" \
105 -d '{
106 "text": "Hello from the marketing bot!"
107 }'
108```
109 
110### Send a Message with Username and Icon Override
111 
112```bash
113curl -s -X POST "$SLACK_WEBHOOK_URL" \
114 -H "Content-Type: application/json" \
115 -d '{
116 "text": "New blog post published!",
117 "username": "Marketing Bot",
118 "icon_emoji": ":mega:"
119 }'
120```
121 
122### Send a Message with Block Kit (Webhook)
123 
124```bash
125curl -s -X POST "$SLACK_WEBHOOK_URL" \
126 -H "Content-Type: application/json" \
127 -d '{
128 "blocks": [
129 {
130 "type": "header",
131 "text": {
132 "type": "plain_text",
133 "text": "New Product Launch"
134 }
135 },
136 {
137 "type": "section",
138 "text": {
139 "type": "mrkdwn",
140 "text": "*Product X* is now live! Check out the announcement."
141 }
142 },
143 {
144 "type": "divider"
145 },
146 {
147 "type": "section",
148 "text": {
149 "type": "mrkdwn",
150 "text": "Read the full announcement on our blog."
151 },
152 "accessory": {
153 "type": "button",
154 "text": {
155 "type": "plain_text",
156 "text": "Read More"
157 },
158 "url": "https://example.com/blog/launch"
159 }
160 }
161 ]
162 }'
163```
164 
165---
166 
167## Method 2: Slack Web API (Bot Token)
168 
169The Web API provides full control over message delivery, threading, channel management, and more.
170 
171### API Base
172 
173All requests go to `https://slack.com/api/` with the header `Authorization: Bearer {SLACK_BOT_TOKEN}`.
174 
175### Post a Message to a Channel
176 
177```bash
178curl -s -X POST "https://slack.com/api/chat.postMessage" \
179 -H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \
180 -H "Content-Type: application/json" \
181 -d '{
182 "channel": "#marketing",
183 "text": "Weekly metrics report is ready!",
184 "blocks": [
185 {
186 "type": "header",
187 "text": {
188 "type": "plain_text",
189 "text": "Weekly Marketing Metrics"
190 }
191 },
192 {
193 "type": "section",
194 "text": {
195 "type": "mrkdwn",
196 "text": "Here are the numbers for this week."
197 }
198 }
199 ]
200 }'
201```
202 
203The response includes a `ts` (timestamp) field which identifies the message. Save this value
204for threading replies:
205 
206```bash
207# Post and capture the message timestamp for threading
208RESPONSE=$(curl -s -X POST "https://slack.com/api/chat.postMessage" \
209 -H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \
210 -H "Content-Type: application/json" \
211 -d '{
212 "channel": "#marketing",
213 "text": "Thread parent message"
214 }')
215 
216MESSAGE_TS=$(echo "$RESPONSE" | python3 -c "import json,sys; print(json.load(sys.stdin).get('ts',''))")
217echo "Message timestamp: $MESSAGE_TS"
218```
219 
220### Reply to a Thread
221 
222Use the `thread_ts` parameter to reply inside an existing thread:
223 
224```bash
225curl -s -X POST "https://slack.com/api/chat.postMessage" \
226 -H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \
227 -H "Content-Type: application/json" \
228 -d "{
229 \"channel\": \"#marketing\",
230 \"thread_ts\": \"${MESSAGE_TS}\",
231 \"text\": \"This is a threaded reply with additional details.\"
232 }"
233```
234 
235To also broadcast the reply to the channel (so it appears in the main conversation as well),
236add `"reply_broadcast": true`.
237 
238### Update an Existing Message
239 
240```bash
241curl -s -X POST "https://slack.com/api/chat.update" \
242 -H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \
243 -H "Content-Type: application/json" \
244 -d "{
245 \"channel\": \"#marketing\",
246 \"ts\": \"${MESSAGE_TS}\",
247 \"text\": \"Updated message content.\",
248 \"blocks\": []
249 }"
250```
251 
252### Delete a Message
253 
254```bash
255curl -s -X POST "https://slack.com/api/chat.delete" \
256 -H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \
257 -H "Content-Type: application/json" \
258 -d "{
259 \"channel\": \"#marketing\",
260 \"ts\": \"${MESSAGE_TS}\"
261 }"
262```
263 
264### List Public Channels
265 
266Useful for discovering which channel to post to:
267 
268```bash
269curl -s "https://slack.com/api/conversations.list?types=public_channel&limit=100" \
270 -H "Authorization: Bearer ${SLACK_BOT_TOKEN}" | \
271 python3 -c "
272import json, sys
273data = json.load(sys.stdin)
274for ch in data.get('channels', []):
275 members = ch.get('num_members', 0)
276 print(f\"#{ch['name']} | Members: {members} | ID: {ch['id']}\")
277"
278```
279 
280### Upload a File to a Channel
281 
282```bash
283curl -s -X POST "https://slack.com/api/files.uploadV2" \
284 -H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \
285 -F "[email protected]" \
286 -F "filename=weekly-report.pdf" \
287 -F "channel_id=C0123456789" \
288 -F "initial_comment=Here is this week's marketing report."
289```
290 
291### Add an Emoji Reaction
292 
293```bash
294curl -s -X POST "https://slack.com/api/reactions.add" \
295 -H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \
296 -H "Content-Type: application/json" \
297 -d "{
298 \"channel\": \"C0123456789\",
299 \"timestamp\": \"${MESSAGE_TS}\",
300 \"name\": \"white_check_mark\"
301 }"
302```
303 
304---
305 
306## Slack Block Kit Reference
307 
308Block Kit is Slack's UI framework for building rich, interactive messages. Messages are composed
309of an array of blocks, each with a specific type and structure.
310 
311### Block Types
312 
313| Block Type | Purpose | Supports mrkdwn |
314|------------|---------|-----------------|
315| `header` | Large bold title text | No (plain_text only) |
316| `section` | Primary content block with text and optional accessory | Yes |
317| `divider` | Horizontal line separator | N/A |
318| `image` | Full-width image with alt text | N/A |
319| `context` | Small, muted text and images (for metadata, timestamps) | Yes |
320| `actions` | Row of interactive elements (buttons, selects, date pickers) | N/A |
321| `rich_text` | Advanced formatted text (lists, quotes, code blocks) | N/A |
322 
323### Header Block
324 
325```json
326{
327 "type": "header",
328 "text": {
329 "type": "plain_text",
330 "text": "Weekly Marketing Report",
331 "emoji": true
332 }
333}
334```
335 
336### Section Block
337 
338Plain text section:
339 
340```json
341{
342 "type": "section",
343 "text": {
344 "type": "mrkdwn",
345 "text": "*Traffic is up 23%* this week compared to last week.\nOrganic search drove most of the growth."
346 }
347}
348```
349 
350Section with fields (two-column layout):
351 
352```json
353{
354 "type": "section",
355 "fields": [
356 {
357 "type": "mrkdwn",
358 "text": "*Visitors*\n12,450"
359 },
360 {
361 "type": "mrkdwn",
362 "text": "*Signups*\n342"
363 },
364 {
365 "type": "mrkdwn",
366 "text": "*MRR*\n$28,500"
367 },
368 {
369 "type": "mrkdwn",
370 "text": "*Churn*\n1.2%"
371 }
372 ]
373}
374```
375 
376Section with a button accessory:
377 
378```json
379{
380 "type": "section",
381 "text": {
382 "type": "mrkdwn",
383 "text": "New blog post: *How We Grew 10x in 6 Months*"
384 },
385 "accessory": {
386 "type": "button",
387 "text": {
388 "type": "plain_text",
389 "text": "Read Post"
390 },
391 "url": "https://example.com/blog/growth-story",
392 "action_id": "read_blog_post"
393 }
394}
395```
396 
397Section with an image accessory:
398 
399```json
400{
401 "type": "section",
402 "text": {
403 "type": "mrkdwn",
404 "text": "*New Feature: Dark Mode*\nOur most requested feature is finally here."
405 },
406 "accessory": {
407 "type": "image",
408 "image_url": "https://example.com/images/dark-mode-preview.png",
409 "alt_text": "Dark mode preview"
410 }
411}
412```
413 
414### Divider Block
415 
416```json
417{
418 "type": "divider"
419}
420```
421 
422### Image Block
423 
424```json
425{
426 "type": "image",
427 "image_url": "https://example.com/images/chart.png",
428 "alt_text": "Weekly traffic chart",
429 "title": {
430 "type": "plain_text",
431 "text": "Traffic Overview"
432 }
433}
434```
435 
436### Context Block
437 
438```json
439{
440 "type": "context",
441 "elements": [
442 {
443 "type": "mrkdwn",
444 "text": "Posted by *Marketing Team* | Feb 10, 2026"
445 },
446 {
447 "type": "image",
448 "image_url": "https://example.com/logo-small.png",
449 "alt_text": "Company logo"
450 }
451 ]
452}
453```
454 
455### Actions Block (Buttons)
456 
457```json
458{
459 "type": "actions",
460 "elements": [
461 {
462 "type": "button",
463 "text": {
464 "type": "plain_text",
465 "text": "Approve"
466 },
467 "style": "primary",
468 "action_id": "approve_action",
469 "value": "approved"
470 },
471 {
472 "type": "button",
473 "text": {
474 "type": "plain_text",
475 "text": "Reject"
476 },
477 "style": "danger",
478 "action_id": "reject_action",
479 "value": "rejected"
480 },
481 {
482 "type": "button",
483 "text": {
484 "type": "plain_text",
485 "text": "View Details"
486 },
487 "url": "https://example.com/details",
488 "action_id": "view_details"
489 }
490 ]
491}
492```
493 
494Button styles: `"primary"` (green), `"danger"` (red), or omit for default (gray).
495 
496**Note on interactivity:** Buttons with `action_id` (no `url`) require a Request URL configured
497in your Slack App settings under **Interactivity & Shortcuts** to receive the button click
498payload. Buttons with a `url` field open the link directly and do not require a backend.
499 
500### Block Kit Limits
501 
502| Limit | Value |
503|-------|-------|
504| Blocks per message | 50 |
505| Characters per text block | 3,000 |
506| Characters per header | 150 |
507| Fields per section | 10 |
508| Elements per actions block | 25 |
509| Elements per context block | 10 |
510 
511### Block Kit Builder
512 
513Use the visual builder to design and preview messages before coding them:
514https://app.slack.com/block-kit-builder
515 
516---
517 
518## Slack mrkdwn Formatting Guide
519 
520Slack uses its own markdown variant called mrkdwn. It differs from standard Markdown in
521several ways.
522 
523| Formatting | Syntax | Example |
524|-----------|--------|---------|
525| Bold | `*text*` | *bold text* |
526| Italic | `_text_` | _italic text_ |
527| Strikethrough | `~text~` | ~strikethrough~ |
528| Code (inline) | `` `text` `` | `inline code` |
529| Code block | ` ```text``` ` | Multi-line code block |
530| Blockquote | `>text` | Quoted text |
531| Link | `<https://url\|display text>` | Clickable link |
532| User mention | `<@U0123456>` | @username |
533| Channel mention | `<#C0123456>` | #channel |
534| Emoji | `:emoji_name:` | :rocket: |
535| Bulleted list | Start line with `- ` or `* ` | Bullet point |
536| Numbered list | Start line with `1. ` | Numbered item |
537| Line break | `\n` in JSON string | New line |
538 
539**Important differences from standard Markdown:**
540- Bold uses single asterisks `*bold*`, not double `**bold**`.
541- Italic uses underscores `_italic_`, not single asterisks.
542- Links use `<url|text>` format with a pipe, not `[text](url)`.
543- Headers do not exist in mrkdwn. Use the `header` block type instead.
544- Images cannot be inlined in mrkdwn. Use the `image` block or `accessory` instead.
545 
546---
547 
548## Message Templates
549 
550### Template 1: Product Announcement
551 
552```bash
553curl -s -X POST "$SLACK_WEBHOOK_URL" \
554 -H "Content-Type: application/json" \
555 -d '{
556 "blocks": [
557 {
558 "type": "header",
559 "text": {
560 "type": "plain_text",
561 "text": ":rocket: New Feature: [Feature Name]",
562 "emoji": true
563 }
564 },
565 {
566 "type": "section",
567 "text": {
568 "type": "mrkdwn",
569 "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]"
570 }
571 },
572 {
573 "type": "image",
574 "image_url": "https://example.com/feature-screenshot.png",
575 "alt_text": "Feature screenshot"
576 },
577 {
578 "type": "divider"
579 },
580 {
581 "type": "actions",
582 "elements": [
583 {
584 "type": "button",
585 "text": {
586 "type": "plain_text",
587 "text": ":newspaper: Read Announcement",
588 "emoji": true
589 },
590 "url": "https://example.com/blog/feature-launch",
591 "action_id": "read_announcement"
592 },
593 {
594 "type": "button",
595 "text": {
596 "type": "plain_text",
597 "text": ":play_or_pause_button: Watch Demo",
598 "emoji": true
599 },
600 "url": "https://example.com/demo",
601 "action_id": "watch_demo"
602 }
603 ]
604 },
605 {
606 "type": "context",
607 "elements": [
608 {
609 "type": "mrkdwn",
610 "text": "Posted by *Product Team* | :speech_balloon: Reply in thread with questions"
611 }
612 ]
613 }
614 ]
615 }'
616```
617 
618### Template 2: Weekly Metrics Report
619 
620```bash
621curl -s -X POST "$SLACK_WEBHOOK_URL" \
622 -H "Content-Type: application/json" \
623 -d '{
624 "blocks": [
625 {
626 "type": "header",
627 "text": {
628 "type": "plain_text",
629 "text": ":bar_chart: Weekly Marketing Metrics — Feb 3-9, 2026",
630 "emoji": true
631 }
632 },
633 {
634 "type": "section",
635 "text": {
636 "type": "mrkdwn",
637 "text": "Here is this week'\''s performance snapshot."
638 }
639 },
640 {
641 "type": "divider"
642 },
643 {
644 "type": "section",
645 "text": {
646 "type": "mrkdwn",
647 "text": ":globe_with_meridians: *Website Traffic*"
648 },
649 "fields": [
650 {
651 "type": "mrkdwn",
652 "text": "*Sessions*\n45,230 (:arrow_up: 12%)"
653 },
654 {
655 "type": "mrkdwn",
656 "text": "*Unique Visitors*\n31,870 (:arrow_up: 8%)"
657 },
658 {
659 "type": "mrkdwn",
660 "text": "*Bounce Rate*\n42.3% (:arrow_down: 2.1%)"
661 },
662 {
663 "type": "mrkdwn",
664 "text": "*Avg. Session Duration*\n3m 42s (:arrow_up: 15s)"
665 }
666 ]
667 },
668 {
669 "type": "section",
670 "text": {
671 "type": "mrkdwn",
672 "text": ":money_with_wings: *Conversion Metrics*"
673 },
674 "fields": [
675 {
676 "type": "mrkdwn",
677 "text": "*Signups*\n487 (:arrow_up: 18%)"
678 },
679 {
680 "type": "mrkdwn",
681 "text": "*Trial-to-Paid*\n12.4% (:arrow_up: 1.2%)"
682 },
683 {
684 "type": "mrkdwn",
685 "text": "*MRR*\n$52,300 (:arrow_up: $3,200)"
686 },
687 {
688 "type": "mrkdwn",
689 "text": "*Churn*\n1.8% (:arrow_down: 0.3%)"
690 }
691 ]
692 },
693 {
694 "type": "section",
695 "text": {
696 "type": "mrkdwn",
697 "text": ":email: *Email Performance*"
698 },
699 "fields": [
700 {
701 "type": "mrkdwn",
702 "text": "*Emails Sent*\n12,400"
703 },
704 {
705 "type": "mrkdwn",
706 "text": "*Open Rate*\n34.2%"
707 },
708 {
709 "type": "mrkdwn",
710 "text": "*Click Rate*\n4.8%"
711 },
712 {
713 "type": "mrkdwn",
714 "text": "*Unsubscribes*\n23"
715 }
716 ]
717 },
718 {
719 "type": "divider"
720 },
721 {
722 "type": "section",
723 "text": {
724 "type": "mrkdwn",
725 "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"
726 }
727 },
728 {
729 "type": "actions",
730 "elements": [
731 {
732 "type": "button",
733 "text": {
734 "type": "plain_text",
735 "text": "Full Dashboard",
736 "emoji": true
737 },
738 "url": "https://analytics.example.com/dashboard",
739 "action_id": "view_dashboard"
740 }
741 ]
742 },
743 {
744 "type": "context",
745 "elements": [
746 {
747 "type": "mrkdwn",
748 "text": "Auto-generated by Marketing Bot | Data from Google Analytics + Stripe"
749 }
750 ]
751 }
752 ]
753 }'
754```
755 
756### Template 3: Blog Post Share
757 
758```bash
759curl -s -X POST "$SLACK_WEBHOOK_URL" \
760 -H "Content-Type: application/json" \
761 -d '{
762 "blocks": [
763 {
764 "type": "header",
765 "text": {
766 "type": "plain_text",
767 "text": ":pencil: New Blog Post Published",
768 "emoji": true
769 }
770 },
771 {
772 "type": "section",
773 "text": {
774 "type": "mrkdwn",
775 "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."
776 },
777 "accessory": {
778 "type": "image",
779 "image_url": "https://example.com/blog/post-og-image.png",
780 "alt_text": "Blog post cover image"
781 }
782 },
783 {
784 "type": "context",
785 "elements": [
786 {
787 "type": "mrkdwn",
788 "text": ":bust_in_silhouette: Author: *Jane Smith* | :clock1: 6 min read | :label: SEO, Growth"
789 }
790 ]
791 },
792 {
793 "type": "divider"
794 },
795 {
796 "type": "section",
797 "text": {
798 "type": "mrkdwn",
799 "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]._"
800 }
801 },
802 {
803 "type": "actions",
804 "elements": [
805 {
806 "type": "button",
807 "text": {
808 "type": "plain_text",
809 "text": "Read the Post",
810 "emoji": true
811 },
812 "style": "primary",
813 "url": "https://example.com/blog/post-slug",
814 "action_id": "read_post"
815 },
816 {
817 "type": "button",
818 "text": {
819 "type": "plain_text",
820 "text": "Share on Twitter",
821 "emoji": true
822 },
823 "url": "https://twitter.com/intent/tweet?text=Check%20out%20this%20post&url=https://example.com/blog/post-slug",
824 "action_id": "share_twitter"
825 },
826 {
827 "type": "button",
828 "text": {
829 "type": "plain_text",
830 "text": "Share on LinkedIn",
831 "emoji": true
832 },
833 "url": "https://www.linkedin.com/sharing/share-offsite/?url=https://example.com/blog/post-slug",
834 "action_id": "share_linkedin"
835 }
836 ]
837 }
838 ]
839 }'
840```
841 
842### Template 4: Team Update / Standup
843 
844```bash
845curl -s -X POST "$SLACK_WEBHOOK_URL" \
846 -H "Content-Type: application/json" \
847 -d '{
848 "blocks": [
849 {
850 "type": "header",
851 "text": {
852 "type": "plain_text",
853 "text": ":clipboard: Marketing Team Update — Monday, Feb 10",
854 "emoji": true
855 }
856 },
857 {
858 "type": "section",
859 "text": {
860 "type": "mrkdwn",
861 "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)"
862 }
863 },
864 {
865 "type": "section",
866 "text": {
867 "type": "mrkdwn",
868 "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"
869 }
870 },
871 {
872 "type": "section",
873 "text": {
874 "type": "mrkdwn",
875 "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"
876 }
877 },
878 {
879 "type": "section",
880 "text": {
881 "type": "mrkdwn",
882 "text": "*:warning: Blockers*\n- Waiting on design team for Product Hunt gallery images\n- Need legal review on new case study before publishing"
883 }
884 },
885 {
886 "type": "divider"
887 },
888 {
889 "type": "context",
890 "elements": [
891 {
892 "type": "mrkdwn",
893 "text": ":speech_balloon: Reply in thread with your own updates or questions"
894 }
895 ]
896 }
897 ]
898 }'
899```
900 
901### Template 5: Incident / Urgent Notification
902 
903```bash
904curl -s -X POST "$SLACK_WEBHOOK_URL" \
905 -H "Content-Type: application/json" \
906 -d '{
907 "blocks": [
908 {
909 "type": "header",
910 "text": {
911 "type": "plain_text",
912 "text": ":rotating_light: Marketing Alert",
913 "emoji": true
914 }
915 },
916 {
917 "type": "section",
918 "text": {
919 "type": "mrkdwn",
920 "text": "*Issue:* [Brief description of the problem]\n*Impact:* [Who/what is affected]\n*Status:* :red_circle: Active\n*Owner:* <@U0123456>"
921 }
922 },
923 {
924 "type": "section",
925 "text": {
926 "type": "mrkdwn",
927 "text": "*Details:*\n[Longer explanation. What happened, when it started, what we know so far.]"
928 }
929 },
930 {
931 "type": "section",
932 "text": {
933 "type": "mrkdwn",
934 "text": "*Next Steps:*\n1. [Action item 1]\n2. [Action item 2]\n3. [Action item 3]"
935 }
936 },
937 {
938 "type": "actions",
939 "elements": [
940 {
941 "type": "button",
942 "text": {
943 "type": "plain_text",
944 "text": "Status Page"
945 },
946 "url": "https://status.example.com",
947 "action_id": "status_page"
948 }
949 ]
950 }
951 ]
952 }'
953```
954 
955---
956 
957## Workflows
958 
959### Workflow 1: Post a Marketing Announcement
960 
961When the user asks to post an announcement, product update, or news to Slack:
962 
9631. **Gather details** - Ask for the announcement title, body, link, image URL, and target channel.
9642. **Choose a template** - Select from the templates above or build a custom Block Kit payload.
9653. **Build the payload** - Construct the JSON with proper mrkdwn formatting.
9664. **Preview** - Show the user the full JSON payload and describe how it will render.
9675. **Confirm** - Ask the user to approve before sending.
9686. **Send** - Execute the curl command.
9697. **Report** - Show the API response. For Web API, capture the `ts` for threading.
970 
971### Workflow 2: Post Weekly Metrics
972 
973When the user asks to send a metrics report or dashboard to Slack:
974 
9751. **Collect metrics** - Ask for the numbers or help pull them from analytics tools.
9762. **Format with fields** - Use section blocks with `fields` for the two-column metric layout.
9773. **Add context** - Include week-over-week comparisons with arrow emoji.
9784. **Add takeaways** - Summarize 2-3 key insights in a section block.
9795. **Include a dashboard link** - Add a button to the full analytics dashboard.
9806. **Send and thread** - Post the main report, then thread detailed breakdowns as replies.
981 
982### Workflow 3: Share a Blog Post
983 
984When a new blog post needs to be distributed to the team:
985 
9861. **Get the URL** - Ask for the blog post URL, or fetch the latest from the blog.
9872. **Extract metadata** - Use WebFetch to pull the title, description, author, and OG image.
9883. **Build the share message** - Use Template 3 with social amplification snippets.
9894. **Add share buttons** - Include Twitter and LinkedIn intent URLs pre-populated with the post.
9905. **Post to the channel** - Send to the team marketing channel.
991 
992### Workflow 4: Community Engagement
993 
994For managing Slack community channels (public communities, customer channels):
995 
9961. **Welcome messages** - Post a welcome message with rules and resources when new members join.
9972. **Scheduled updates** - Post weekly roundups of popular discussions or new resources.
9983. **Event announcements** - Share upcoming webinars, AMAs, or meetups with RSVP buttons.
9994. **Polls and feedback** - Use actions blocks with buttons to collect quick feedback.
10005. **Thread management** - Reply to existing threads with updates or answers.
1001 
1002---
1003 
1004## Sending Messages with Dynamic Content
1005 
1006### Build Payloads with Shell Variables
1007 
1008```bash
1009TITLE="Product X v2.0 Released"
1010DESCRIPTION="Version 2.0 includes dark mode, API improvements, and 3x faster performance."
1011LINK="https://example.com/changelog/v2"
1012IMAGE_URL="https://example.com/images/v2-banner.png"
1013CHANNEL="#announcements"
1014 
1015curl -s -X POST "https://slack.com/api/chat.postMessage" \
1016 -H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \
1017 -H "Content-Type: application/json" \
1018 -d "$(python3 -c "
1019import json
1020payload = {
1021 'channel': '${CHANNEL}',
1022 'text': '${TITLE}',
1023 'blocks': [
1024 {
1025 'type': 'header',
1026 'text': {'type': 'plain_text', 'text': '${TITLE}', 'emoji': True}
1027 },
1028 {
1029 'type': 'section',
1030 'text': {'type': 'mrkdwn', 'text': '${DESCRIPTION}'}
1031 },
1032 {
1033 'type': 'image',
1034 'image_url': '${IMAGE_URL}',
1035 'alt_text': '${TITLE}'
1036 },
1037 {
1038 'type': 'actions',
1039 'elements': [{
1040 'type': 'button',
1041 'text': {'type': 'plain_text', 'text': 'Learn More'},
1042 'url': '${LINK}',
1043 'style': 'primary',
1044 'action_id': 'learn_more'
1045 }]
1046 }
1047 ]
1048}
1049print(json.dumps(payload))
1050")"
1051```
1052 
1053### Build Payloads from a JSON File
1054 
1055For complex messages, write the payload to a file first:
1056 
1057```bash
1058# Write the payload
1059cat > /tmp/slack-message.json << 'PAYLOAD'
1060{
1061 "channel": "#marketing",
1062 "text": "Fallback text for notifications",
1063 "blocks": [
1064 {
1065 "type": "header",
1066 "text": {
1067 "type": "plain_text",
1068 "text": "Message Title"
1069 }
1070 },
1071 {
1072 "type": "section",
1073 "text": {
1074 "type": "mrkdwn",
1075 "text": "Message body with *bold* and _italic_ formatting."
1076 }
1077 }
1078 ]
1079}
1080PAYLOAD
1081 
1082# Send it
1083curl -s -X POST "https://slack.com/api/chat.postMessage" \
1084 -H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \
1085 -H "Content-Type: application/json" \
1086 -d @/tmp/slack-message.json
1087```
1088 
1089---
1090 
1091## Scheduled Messages
1092 
1093Post a message at a specific future time using `chat.scheduleMessage`:
1094 
1095```bash
1096# Schedule a message for a specific Unix timestamp
1097# 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)
1098SEND_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)
1099 
1100curl -s -X POST "https://slack.com/api/chat.scheduleMessage" \
1101 -H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \
1102 -H "Content-Type: application/json" \
1103 -d "{
1104 \"channel\": \"#marketing\",
1105 \"post_at\": ${SEND_AT},
1106 \"text\": \"Good morning team! Here is today's marketing agenda.\",
1107 \"blocks\": []
1108 }"
1109```
1110 
1111List scheduled messages:
1112 
1113```bash
1114curl -s "https://slack.com/api/chat.scheduledMessages.list" \
1115 -H "Authorization: Bearer ${SLACK_BOT_TOKEN}" | \
1116 python3 -c "
1117import json, sys, datetime
1118data = json.load(sys.stdin)
1119for msg in data.get('scheduled_messages', []):
1120 ts = datetime.datetime.fromtimestamp(msg['post_at']).strftime('%Y-%m-%d %H:%M')
1121 print(f\"ID: {msg['id']} | Channel: {msg['channel_id']} | Scheduled: {ts}\")
1122"
1123```
1124 
1125Delete a scheduled message:
1126 
1127```bash
1128curl -s -X POST "https://slack.com/api/chat.deleteScheduledMessage" \
1129 -H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \
1130 -H "Content-Type: application/json" \
1131 -d '{
1132 "channel": "C0123456789",
1133 "scheduled_message_id": "Q0123456789"
1134 }'
1135```
1136 
1137---
1138 
1139## Multi-Channel Posting
1140 
1141Post the same message to multiple channels:
1142 
1143```bash
1144CHANNELS=("#marketing" "#general" "#product")
1145MESSAGE='{"text":"Big announcement coming tomorrow!","blocks":[{"type":"section","text":{"type":"mrkdwn","text":":mega: *Big announcement coming tomorrow!* Stay tuned."}}]}'
1146 
1147for CHANNEL in "${CHANNELS[@]}"; do
1148 echo "Posting to ${CHANNEL}..."
1149 echo "$MESSAGE" | python3 -c "
1150import json, sys
1151msg = json.load(sys.stdin)
1152msg['channel'] = '${CHANNEL}'
1153print(json.dumps(msg))
1154" | curl -s -X POST "https://slack.com/api/chat.postMessage" \
1155 -H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \
1156 -H "Content-Type: application/json" \
1157 -d @- | python3 -c "
1158import json, sys
1159r = json.load(sys.stdin)
1160if r.get('ok'):
1161 print(f' Sent. ts={r[\"ts\"]}')
1162else:
1163 print(f' Error: {r.get(\"error\", \"unknown\")}')
1164"
1165done
1166```
1167 
1168---
1169 
1170## Error Handling
1171 
1172### Common API Errors
1173 
1174| Error | Cause | Fix |
1175|-------|-------|-----|
1176| `invalid_auth` | Bad or expired token | Regenerate the bot token in Slack App settings |
1177| `channel_not_found` | Bot not in channel or wrong channel name | Invite bot with `/invite @BotName` or use channel ID |
1178| `not_in_channel` | Bot needs to join the channel first | Invite the bot or use `chat:write.public` scope |
1179| `too_many_attachments` | Over 50 blocks | Split the message into multiple posts or thread replies |
1180| `msg_too_long` | Text exceeds 40,000 characters | Shorten the message or split into parts |
1181| `rate_limited` | Too many requests | Wait the number of seconds in the `Retry-After` header |
1182| `missing_scope` | Token lacks required permission | Add the scope in OAuth & Permissions and reinstall the app |
1183 
1184### Validate a Response
1185 
1186```bash
1187RESPONSE=$(curl -s -X POST "https://slack.com/api/chat.postMessage" \
1188 -H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \
1189 -H "Content-Type: application/json" \
1190 -d '{"channel":"#marketing","text":"Test message"}')
1191 
1192python3 -c "
1193import json, sys
1194r = json.loads('${RESPONSE}'.replace(\"'\", \"\"))
1195if r.get('ok'):
1196 print(f'Message sent successfully. ts={r[\"ts\"]} channel={r[\"channel\"]}')
1197else:
1198 print(f'Error: {r.get(\"error\", \"unknown\")}')
1199 if r.get('response_metadata', {}).get('messages'):
1200 for m in r['response_metadata']['messages']:
1201 print(f' Detail: {m}')
1202" 2>/dev/null || echo "$RESPONSE"
1203```
1204 
1205A more robust approach using a temp file:
1206 
1207```bash
1208RESPONSE_FILE=$(mktemp)
1209curl -s -X POST "https://slack.com/api/chat.postMessage" \
1210 -H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \
1211 -H "Content-Type: application/json" \
1212 -d '{"channel":"#marketing","text":"Test message"}' \
1213 -o "$RESPONSE_FILE"
1214 
1215python3 -c "
1216import json
1217with open('${RESPONSE_FILE}') as f:
1218 r = json.load(f)
1219if r.get('ok'):
1220 print(f'Sent. ts={r[\"ts\"]}')
1221else:
1222 print(f'Error: {r.get(\"error\")}')
1223"
1224rm -f "$RESPONSE_FILE"
1225```
1226 
1227---
1228 
1229## Tips
1230 
1231- Always include a `text` field alongside `blocks` — it serves as the fallback for
1232 notifications, accessibility readers, and clients that do not support Block Kit.
1233- Use the Block Kit Builder at https://app.slack.com/block-kit-builder to visually design
1234 and preview messages before building the curl commands.
1235- For production workflows, use `chat.postMessage` (Web API) over webhooks. It returns a
1236 message `ts` you can use for threading, updating, and deleting.
1237- Thread long reports. Post a summary as the parent message and details as threaded replies
1238 to keep channels clean.
1239- Use `:emoji:` codes in `plain_text` fields with `"emoji": true` to render emoji in headers
1240 and button labels.
1241- Escape special characters in mrkdwn: `&` becomes `&amp;`, `<` becomes `&lt;`, `>` becomes `&gt;`.
1242- Rate limits: Slack allows roughly 1 message per second per channel. For bulk posting, add a
1243 1-second delay between requests.
1244- When posting metrics, use section `fields` for the two-column layout rather than trying to
1245 format tables in mrkdwn (Slack does not support tables).
1246- **Always show the user the full message payload and ask for confirmation before posting.**
1247 

Discussion

Alternatives

API and interface designGuides stable API and interface design. Use when designing APIs, module boundaries, or any public interface. Use when creating REST or GraphQL endpoints, defining type contracts between modules, or establishing boundaries between frontend and backend.Coding · MITContext7Pulls up-to-date, version-specific library docs and code examples into the prompt so the AI stops inventing old APIs.Coding · MITContext7 Documentation LookupFetch up-to-date documentation and code examples for any library, framework, SDK, CLI tool, or cloud service. Use whenever the user asks about a specific library — even well-known ones like React, Next.js, Prisma, Express, Tailwind, Django, or Spring Boot — because training data may not reflect recent API changes or version updates. Always use for: API syntax questions, configuration options, version migration issues, "how do I" questions mentioning a library name, debugging that involves library-specific behavior, setup instructions, and CLI tool usage. Use even when you think you know the answer. Do not rely on training data for API details, signatures, or configuration options — they are frequently out of date. Prefer this over web search for library documentation.Coding · MITAdaptyv Bio Foundry APIHow to use the Adaptyv Bio Foundry API and Python SDK for protein experiment design, submission, and results retrieval. Use this skill whenever the user mentions Adaptyv, Foundry API, protein binding assays, protein screening experiments, BLI/SPR assays, thermostability assays, or wants to submit protein sequences for experimental characterization. Also trigger when code imports `adaptyv`, `adaptyv_sdk`, or `FoundryClient`, or references `foundry-api-public.adaptyvbio.com`.Science · MIT