Skip to content

Ops

Discord & Slack Bots — Internal Productivity Plays (Node.js Patterns)

A custom Slack bot for your team can save more hours than a $500/month SaaS tool. Standup summariser that reads your messages and writes your daily standup. Deploy notifier that posts when your CI passes (or fails). Support-ticket router that auto-assigns Slack threads. On-call rotation that pages you on Slack. Four patterns Aidxn ships, minimal Node.js setup, and 6 FAQs.

🤖 💬

Your team ships on GitHub. CI runs on GitHub Actions. When a deploy finishes, someone manually checks the logs, then posts "deploy successful" in Slack. Takes 30 seconds per deploy. 10 deploys a day × 30 seconds = 5 minutes wasted. Multiply across a team of 5 and that's 25 minutes of context-switching per day just to relay a status that your CI already knows.

Your support channel is chaos. Tickets arrive as Slack threads. Support team reads thread, opens a ticket in Linear (or Jira, or Asana). Then someone assigns it. Then it sits in a queue because nobody knows whose turn it is. A customer's bug isn't fixed, it's just shuffled between tabs. Meanwhile, your on-call engineer has no idea there's a production issue brewing because they're not in the support Slack workspace.

Your standup is 30 minutes of "what did you do yesterday?" in a video call. Everyone already typed it in a Slack thread. But you read it out loud anyway. Efficiency is zero. Async is better — but who's gonna read 12 threads and write a standup summary? Nobody. So it doesn't happen.

These aren't SaaS problems. They're automation problems. You've already got Slack or Discord. You've got a database and an API. A bot costs $50 and 4 hours to build. It saves your team 30+ hours a month. That's a 150× ROI. Here's how Aidxn builds them and why your team should too.

Why Bots Beat SaaS for Internal Tools

SaaS tools solve real problems. But they come with friction: login, new UI, another tab, another notification, data silos. Your team uses Slack all day. If you can do it in Slack, you don't need a separate tool. A bot lives in Slack. It reads context from your existing channels. It doesn't require a login or a new dashboard. It posts results where people already work. Friction drops to zero. Adoption is instant.

The ROI math: A team Slack bot costs $0 (Slack's free tier is enough) plus hosting ($5-20/month) plus engineering time. A month of SaaS tool costs $500+. A bot pays for itself in one week if it saves 2 hours of team time. Most bots save 10+ hours. We've shipped bots that replaced $5K/year in SaaS spend.

The data advantage: A bot is your code. Your data never touches a third party. No vendor lock-in. No surprise API deprecations. No pricing changes. If Slack changes their API, you update your bot. If a SaaS tool shuts down, you're migrating everything.

Here are the four patterns Aidxn ships most often. Each one is genuinely useful and takes less than a day to build.

Pattern 1: Standup Summariser

The problem: Your team's already doing async standup in a Slack thread. Someone needs to read 12 threads and write a standup summary for stakeholders. It's tedious, nobody volunteers, so the summary doesn't exist.

The solution: A bot that watches your #standup channel. Every morning at 10am (when all the async messages are in), the bot reads every thread in #standup, calls Claude API to summarise each one into a bullet point, and posts a compiled standup summary. Same threads, zero manual effort.

// bot.js — Standup Summariser
const { Slack } = require('@slack/bolt');
const Anthropic = require('@anthropic-ai/sdk');

const app = new Slack({
  token: process.env.SLACK_BOT_TOKEN,
  signingSecret: process.env.SLACK_SIGNING_SECRET,
});

const client = new Anthropic();

// Runs daily at 10am via cron job
async function summarizeStandups() {
  const channel = await app.client.conversations.info({ channel: 'C0LUN3Q0L' });
  const history = await app.client.conversations.history({
    channel: 'C0LUN3Q0L',
    oldest: Math.floor(Date.now() / 1000) - 86400, // last 24h
  });

  let summary = '📋 **Daily Standup Summary**\n\n';

  for (const message of history.messages) {
    if (!message.thread_ts) continue; // Skip non-thread messages

    const replies = await app.client.conversations.replies({
      channel: 'C0LUN3Q0L',
      ts: message.thread_ts,
    });

    const threadText = replies.messages
      .map(m => m.text || '')
      .filter(Boolean)
      .join('\n');

    const response = await client.messages.create({
      model: 'claude-opus-4-1',
      max_tokens: 100,
      messages: [{
        role: 'user',
        content: `Summarise this standup thread in 1-2 bullet points:\n${threadText}`,
      }],
    });

    summary += `• ${response.content[0].text}\n`;
  }

  await app.client.chat.postMessage({
    channel: 'C0LUN3Q0L',
    text: summary,
  });
}

// Trigger via cron (node-cron or external scheduler)
const cron = require('node-cron');
cron.schedule('0 10 * * 1-5', summarizeStandups); // Weekdays at 10am

app.start();

Deploy this bot to a simple Node.js server (Heroku, Fly.io, Replit, whatever). Set up a cron job for 10am weekdays. Every morning the bot reads all standup threads from the last 24 hours, calls Claude API with each thread, compiles a summary, posts it to #standup. Your team reads one message instead of twelve. Managers get a rollup. Takes 2 hours to build. Saves 4 hours per week.

Pattern 2: Deploy Notifier

The problem: Your CI runs on GitHub Actions (or GitLab CI, or CircleCI). When a deploy succeeds, someone has to remember to tell the team. Slack has 3K messages a day. "Deploy to prod succeeded" gets lost in the noise.

The solution: GitHub Actions webhook that posts to Slack with a pinned message in a dedicated #deploys channel. Include branch name, commit message, deployment status, and a link to the workflow run. Add emoji indicators (✅ for success, ❌ for failure). Done.

// GitHub Actions workflow (.github/workflows/deploy.yml)
name: Build & Deploy

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Build
        run: npm run build
      - name: Deploy
        run: npm run deploy
      - name: Notify Slack
        if: always()
        uses: slackapi/slack-github-action@v1.24.0
        with:
          payload: |
            {
              "text": "${{ job.status == 'success' && '✅' || '❌' }} Deploy ${{ github.ref_name }} (${{ github.event.head_commit.message }})",
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "*${{ job.status == 'success' && '✅ Deploy Successful' || '❌ Deploy Failed' }}*\nBranch: \`${{ github.ref_name }}\`\nCommit: <${{ github.event.head_commit.url }}|${{ github.event.head_commit.message }}>\nWorkflow: <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Details>"
                  }
                }
              ]
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
          SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK

GitHub Actions has a built-in Slack integration. No bot code needed. Define a Slack incoming webhook URL as a secret in your GitHub repo, then post a structured message when your workflow completes. Include status, branch, commit message, and a link to the workflow run. Everyone sees deploys in real-time. On-call engineer knows when to roll back. QA knows when to test. No missed updates.

Pattern 3: Support Ticket Router

The problem: Customer emails arrive in a shared inbox. Support team reads them, posts to #support Slack channel as threads. But there's no queue. No one owns the ticket. It sits until someone notices. Meanwhile a customer's waiting for a response.

The solution: A bot that watches #support and auto-assigns threads to the on-call support engineer. When a new message starts a thread, the bot pings the assigned engineer. Includes customer name, issue summary (via Claude), and a link to the original email/ticket system. Thread becomes the ticket — no context switch, no Linear, no Jira, no tab hell.

// support-router.js
const { Slack } = require('@slack/bolt');
const Anthropic = require('@anthropic-ai/sdk');

const app = new Slack({
  token: process.env.SLACK_BOT_TOKEN,
  signingSecret: process.env.SLACK_SIGNING_SECRET,
});

const client = new Anthropic();

// Get the on-call engineer from your DB / spreadsheet / whatever
async function getOncallEngineer() {
  // Example: fetch from Supabase
  const response = await fetch('https://your-project.supabase.co/rest/v1/oncall', {
    headers: { 'Authorization': `Bearer ${process.env.SUPABASE_KEY}` },
  });
  const [oncall] = await response.json();
  return oncall.slack_user_id;
}

app.event('app_mention', async ({ event, say }) => {
  if (event.channel !== 'C0LUN3Q0L') return; // only in #support

  // Summarise the issue
  const response = await client.messages.create({
    model: 'claude-opus-4-1',
    max_tokens: 150,
    messages: [{
      role: 'user',
      content: `Summarise this support ticket in 1-2 sentences:\n${event.text}`,
    }],
  });

  const summary = response.content[0].text;
  const oncallId = await getOncallEngineer();

  await say({
    thread_ts: event.ts,
    text: `🎫 <@${oncallId}> assigned to this ticket\n\n${summary}`,
  });

  // Send DM to on-call engineer
  await app.client.chat.postMessage({
    channel: oncallId,
    text: `New support ticket in <#C0LUN3Q0L|#support>: ${summary}`,
  });
});

app.start();

When a new message mentions the bot in #support (or you use a specific keyword like "ticket"), the bot summarises the issue in 1-2 sentences, assigns it to the on-call engineer, and sends them a DM. They click the link, reply in the thread. That thread IS the ticket — no bouncing between systems. Faster response time. Happy customers. Happy engineers.

Pattern 4: On-Call Rotation Manager

The problem: Your on-call schedule lives in PagerDuty (or Google Cal, or a spreadsheet). When something breaks, nobody knows who's on-call. They page the wrong person. Or nobody pages anyone and the issue goes dark.

The solution: A bot that knows your on-call schedule. When someone types `/oncall` in Slack, the bot replies with who's on-call, how long until handoff, and a link to the page them (PagerDuty). If a critical alert comes in, the bot auto-pages the on-call engineer (or posts to #incidents and tags them).

// oncall-manager.js
const { Slack } = require('@slack/bolt');

const app = new Slack({
  token: process.env.SLACK_BOT_TOKEN,
  signingSecret: process.env.SLACK_SIGNING_SECRET,
});

// Fetch on-call schedule from PagerDuty or your DB
async function getOncall() {
  const response = await fetch('https://api.pagerduty.com/oncalls', {
    headers: { 'Authorization': `Token token=${process.env.PAGERDUTY_API_KEY}` },
  });
  const data = await response.json();
  return data.oncalls[0]; // First on-call
}

app.command('/oncall', async ({ ack, say }) => {
  ack();

  const oncall = await getOncall();
  const handoff = new Date(oncall.end_time);
  const hoursLeft = Math.ceil((handoff - Date.now()) / 3600000);

  await say({
    blocks: [
      {
        type: 'section',
        text: {
          type: 'mrkdwn',
          text: `*🚨 On-Call: ${oncall.user.summary}*\nHandoff in ${hoursLeft}h\n<${process.env.PAGERDUTY_URL}|View Schedule>`,
        },
      },
      {
        type: 'actions',
        elements: [
          {
            type: 'button',
            text: { type: 'plain_text', text: 'Page Now' },
            value: oncall.user.id,
            action_id: 'page_oncall',
          },
        ],
      },
    ],
  });
});

app.action('page_oncall', async ({ ack, respond }) => {
  ack();

  // Trigger PagerDuty incident or send urgent DM
  await respond({ text: '📟 Paging on-call engineer...' });
  // Call PagerDuty API or your incident system
});

app.start();

Slash command `/oncall` shows who's currently on-call, when they hand off, and a button to page them. No need to dig through PagerDuty or Google Calendar. Everyone knows in one click. For critical alerts, the bot can auto-trigger from your monitoring tool (Honeycomb, Sentry, Prometheus) and tag the on-call engineer in #incidents. Faster MTTR. Less confusion.

How to Build a Bot in 20 Minutes

Step 1: Set up Slack bot (5 minutes)

Go to api.slack.com → "Your Apps" → Create New App. Choose "From Scratch". Name: `My Standup Bot` (or whatever). Workspace: your Slack. Go to "Bot Token Scopes" and add permissions: `chat:write` (post messages), `channels:history` (read messages), `conversations.info` (read thread info). "Install App" to your workspace. Copy the Bot User OAuth Token to your `.env` file.

Step 2: Set up Node project (5 minutes)

npm init -y
npm install @slack/bolt @anthropic-ai/sdk node-cron dotenv

Create `.env` with your Slack token and Anthropic API key.

Step 3: Write the bot code (5 minutes)

Copy one of the patterns above. Replace `C0LUN3Q0L` with your actual channel ID (right-click channel → "View channel details"). Update the summarization prompt or logic to fit your use case. Test locally: `node bot.js`.

Step 4: Deploy and automate (5 minutes)

Push to GitHub. Deploy to Fly.io (`flyctl launch`), Heroku, or Replit. For cron tasks (standup summariser, rotation manager), use a separate service (GitHub Actions, node-cron, or a cheap cron service like EasyCron). Done.

Six FAQs

Q: Won't Slack just replace this bot with built-in features?

A: Maybe. Slack released Workflow Builder and now they're adding AI features. But Slack's AI workflows are generic. Your bot is custom — it knows your on-call schedule, your deploy system, your exact team friction. A generic "AI assistant" doesn't solve your specific problem. Plus you own the code. If Slack kills a feature, your bot stays. Build the bot.

Q: What if someone leaves the team and their messages are in the bot's context?

A: Good question. Use a token-limited API for Claude (set `max_tokens`). For Standup Summariser, you're summarising recent threads daily — no long-term memory. If you're worried about privacy, don't include usernames in prompts. Just send thread text. Claude doesn't store your messages (Anthropic has a 30-day retention policy for API calls, not the actual data).

Q: Can I run this bot on my own infrastructure instead of the cloud?

A: Yes. Run Node.js on a spare laptop, Raspberry Pi, or on-prem server. The bot connects to Slack via API (outbound HTTPS only). You control the data. For cron tasks, use your OS's cron (Linux/Mac) or Task Scheduler (Windows). This is actually more common than you'd think at conservative companies that don't want to use cloud hosting.

Q: How much does this cost?

A: Slack API is free (no call limits). Anthropic Claude API costs ~$0.003 per 1K input tokens. Standup Summariser calls Claude once per day (call costs $0.01). Deploy Notifier is free (GitHub Actions webhook, no Claude). Support Router calls Claude once per ticket (say 50 tickets/month = $0.50). On-Call Manager is free (no Claude, just polling PagerDuty). Total monthly cost for a bot suite: ~$1-3 in API calls plus $5-10 hosting. Versus $500/month SaaS. You do the math.

Q: What if Slack's API breaks or changes?

A: Slack's core API is stable. They deprecate old endpoints slowly (years of notice). When they do, update your bot (usually 5-minute fix). Your bot is code you control — you can test changes before deploying. A SaaS tool just breaks and you wait for the vendor to fix it.

Q: Can I use Discord instead?

A: Absolutely. Replace `@slack/bolt` with Discord.js (or discord.py if you prefer Python). The patterns are identical. Discord is actually easier because it doesn't rate-limit as aggressively. If your team uses Discord for dev/gaming communities, go for it. Same ROI, same code structure.

The Bottom Line

Your team spends 30+ hours a month on friction that doesn't exist in Slack — relay deploys manually, shuffle support tickets between systems, read standups out loud. A bot solves this. Costs $0 to hundreds/month. Takes 4-8 hours to build. Saves 10-40 hours monthly. That's not ROI, that's a cheat code. Build the bot. Aidxn ships them all the time. Drop us a line if you want help wiring one up for your specific team workflow, or dig deeper into issue tracking systems if you're thinking about replacing Linear entirely.

Start with the Standup Summariser. Simplest to build, instant impact, team feels the win on day one.

Let us make some quick suggestions?
Please provide your full name.
Please provide your phone number.
Please provide a valid phone number.
Please provide your email address.
Please provide a valid email address.
Please provide your brand name or website.
Please provide your brand name or website.