惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
Schneier on Security
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
T
Threat Research - Cisco Blogs
C
Cyber Attacks, Cyber Crime and Cyber Security
C
CXSECURITY Database RSS Feed - CXSecurity.com
A
Arctic Wolf
Security Latest
Security Latest
Simon Willison's Weblog
Simon Willison's Weblog
I
Intezer
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
T
Troy Hunt's Blog
Latest news
Latest news
Help Net Security
Help Net Security
S
Security Affairs
Webroot Blog
Webroot Blog
The Hacker News
The Hacker News
AI
AI
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
T
Tor Project blog
Forbes - Security
Forbes - Security
Google DeepMind News
Google DeepMind News
AWS News Blog
AWS News Blog
Attack and Defense Labs
Attack and Defense Labs
P
Proofpoint News Feed
www.infosecurity-magazine.com
www.infosecurity-magazine.com
H
Help Net Security
L
Lohrmann on Cybersecurity
S
SegmentFault 最新的问题
Google Online Security Blog
Google Online Security Blog
MongoDB | Blog
MongoDB | Blog
Cyberwarzone
Cyberwarzone
The Last Watchdog
The Last Watchdog
S
Securelist
N
News and Events Feed by Topic
S
Secure Thoughts
F
Fortinet All Blogs
博客园_首页
C
Cybersecurity and Infrastructure Security Agency CISA
量子位
M
MIT News - Artificial intelligence
F
Full Disclosure
T
The Blog of Author Tim Ferriss
T
Tailwind CSS Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Microsoft Security Blog
Microsoft Security Blog
I
InfoQ
P
Privacy International News Feed
L
LangChain Blog
Know Your Adversary
Know Your Adversary
C
CERT Recently Published Vulnerability Notes

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
How I built a Discord 'ship-tracker' bot in a weekend (and the 3-process architecture that keeps it alive 24/7)
Chalom Ellez · 2026-05-09 · via DEV Community

Disclosure: I'm a senior backend tech lead and I run HostingGuru. This bot runs on HostingGuru's Pro tier — but the architecture (web service + worker + scheduled job) works on any platform that supports those three primitives. I'll point out where each piece runs.


I co-run a small Discord community for indie founders building dev tools. About 220 members, mostly early-stage SaaS people, lots of Claude Code / Cursor enthusiasts. Every Monday I used to manually scroll through the previous week's #i-shipped channel and write a digest message: "this week we shipped X, Y, Z."

It took 30 minutes every Monday morning. After 5 weeks of it I did the math — 30 min × 52 weeks = 26 hours a year of me doing what a bot could do better. So one Saturday I built ShipTrack, the bot that's been keeping my Mondays free for 6 months now.

This is the build log. It's mostly about an architecture decision (3 separate processes instead of 1) that turned out to be the difference between "bot keeps crashing" and "bot just works."

What the bot does

Three things, in order of complexity:

  1. Listens for the /ship slash command. When a member runs /ship "Launched my AI todo app — feedback welcome: link.com", the bot logs the launch into a database and reacts with 🚀 in the channel.
  2. Tracks #i-shipped channel messages. When anyone posts in that channel (without slash command), the bot detects launch-shaped content (heuristic: contains a URL + at least one of "shipped", "launched", "live"), logs it, reacts.
  3. Posts a weekly digest every Monday at 9am UTC. The bot pulls all launches from the last 7 days, formats them into a nice list, and posts it to #announcements with @-mentions of the founders.

That's it. Three things. But they map to three completely different kinds of computation, which is where v1 went wrong.

v1: the naive setup that crashed in 15 minutes

I started simple. One Node.js file. node bot.js. Deploy to a Render free web service. Done in 30 minutes.

It worked on my laptop. It worked for the first 14 minutes after deploy. Then Render's free tier put the service to sleep due to no incoming HTTP traffic — and a Discord bot doesn't get HTTP traffic by default. It maintains a long-lived WebSocket connection to Discord's gateway. Render couldn't see that traffic. To Render, my bot was idle. So Render killed it.

When the bot came back from sleep 30 seconds later, it tried to reconnect to Discord's gateway. Discord saw two sessions for the same bot. The old session got disconnected with a 4008 Reconnect and the new one inherited some weird state. Members started seeing the bot react to messages twice. Slash commands timed out.

This is the kind of bug that takes a long time to diagnose if you've never seen it before, because everything looks fine in your logs. There's no error, just slightly wrong behavior. I wasted 4 hours.

Why Discord bots are weirder than they look

The thing nobody tells you when you start: a Discord bot has two completely different communication channels with Discord's servers, and they have totally different operational requirements.

Channel 1: the gateway (WebSocket, persistent).

  • The bot opens a WebSocket to wss://gateway.discord.gg
  • Stays open forever
  • Receives every event in real time (member joined, message posted, reaction added)
  • Sends heartbeats every 41.25 seconds
  • If the connection drops for >60 seconds, you have to fully re-authenticate and resync state

Channel 2: slash commands (HTTP, on-demand).

  • Discord POSTs to YOUR endpoint when a user runs a slash command
  • You have 3 seconds to respond or Discord shows "interaction failed" to the user
  • Public HTTP endpoint with signed payload verification

These two channels don't fit on the same kind of host. The gateway needs always-on. The slash command webhook needs public HTTPS that wakes up fast. Most "deploy your Node app" flows assume one or the other, not both.

v2: three processes, three responsibilities

The architecture I landed on has three pieces:

┌──────────────────────┐    ┌──────────────────────┐    ┌──────────────────────┐
│  WEB SERVICE          │   │  WORKER               │   │  SCHEDULED SCRIPT     │
│  HTTPS endpoint       │   │  Always-on process    │   │  Runs Monday 9am UTC  │
│  Slash command webhook│   │  Discord gateway      │   │  Generates weekly     │
│  /api/discord/interact│   │  WebSocket connection │   │  digest               │
└──────────────────────┘    └──────────────────────┘    └──────────────────────┘
              │                         │                          │
              └─────────────────────────┴──────────────────────────┘
                                        │
                                ┌──────────────┐
                                │  Postgres    │
                                │  (launches)  │
                                └──────────────┘

Enter fullscreen mode Exit fullscreen mode

Three separate deployments, one shared database. Each process does what it's good at and nothing else.

Process 1: the web service (slash commands)

This is a tiny Express app. One endpoint. Returns under 1 second.

// web-service/server.js
import express from 'express';
import { verifyKey } from 'discord-interactions';
import { db } from './db.js';

const app = express();
app.use(express.json({
  verify: (req, _res, buf) => { req.rawBody = buf; }
}));

app.post('/api/discord/interact', async (req, res) => {
  // 1. Verify Discord signed the request
  const signature = req.get('X-Signature-Ed25519');
  const timestamp = req.get('X-Signature-Timestamp');
  const valid = verifyKey(req.rawBody, signature, timestamp, process.env.DISCORD_PUBLIC_KEY);
  if (!valid) return res.status(401).send('invalid signature');

  // 2. Discord sometimes pings to check liveness
  if (req.body.type === 1) return res.json({ type: 1 });

  // 3. Slash command — log the launch and respond fast
  if (req.body.type === 2 && req.body.data?.name === 'ship') {
    const userId = req.body.member.user.id;
    const username = req.body.member.user.username;
    const text = req.body.data.options?.[0]?.value || '';

    await db.launches.insert({
      user_id: userId,
      username,
      text,
      channel_id: req.body.channel_id,
      created_at: new Date(),
    });

    return res.json({
      type: 4,
      data: { content: `🚀 Logged your ship, ${username}!` },
    });
  }

  res.json({ type: 4, data: { content: 'Unknown command' } });
});

app.listen(process.env.PORT || 3000);

Enter fullscreen mode Exit fullscreen mode

Deploy this as a normal web service. It can sleep on free tiers — Discord sends a request only when someone runs /ship, and 1 second of cold start before responding is fine. (For HostingGuru, I picked the Hobby tier with the always-on free guarantee anyway, but the architecture works either way.)

Process 2: the worker (gateway + reactions)

This is the long-running part. It opens the WebSocket connection to Discord and listens for messages. It can't sleep. Ever.

// worker/bot.js
import { Client, GatewayIntentBits, Events } from 'discord.js';
import { db } from './db.js';

const client = new Client({
  intents: [
    GatewayIntentBits.Guilds,
    GatewayIntentBits.GuildMessages,
    GatewayIntentBits.MessageContent,
  ],
});

const SHIPPED_CHANNEL_ID = process.env.SHIPPED_CHANNEL_ID;

client.on(Events.MessageCreate, async (msg) => {
  if (msg.author.bot) return;
  if (msg.channel.id !== SHIPPED_CHANNEL_ID) return;

  // Heuristic: contains a URL + a "shipped"-ish word
  const hasUrl = /https?:\/\/\S+/.test(msg.content);
  const hasShipWord = /\b(shipped|launched|live|released)\b/i.test(msg.content);
  if (!hasUrl || !hasShipWord) return;

  await db.launches.insert({
    user_id: msg.author.id,
    username: msg.author.username,
    text: msg.content,
    channel_id: msg.channel.id,
    message_id: msg.id,
    created_at: new Date(),
  });

  await msg.react('🚀');
});

client.on(Events.ClientReady, () => {
  console.log(`ShipTrack online as ${client.user.tag}`);
});

client.login(process.env.DISCORD_BOT_TOKEN);

Enter fullscreen mode Exit fullscreen mode

Deploy this as a background worker. On HostingGuru this is the Pro tier worker process type — same Procfile-style declaration as Heroku's old worker: line:

# hostingguru.yml (or similar config)
processes:
  bot:
    type: worker
    command: node worker/bot.js
    always_on: true

Enter fullscreen mode Exit fullscreen mode

The platform keeps it running. If it crashes, it restarts. If you push new code, it gracefully reconnects. No HTTP traffic required to keep it alive — that's the whole point of a worker process type vs a web service.

Process 3: the scheduled script (weekly digest)

This one runs once a week. It's an "on-demand" script — runs, finishes, exits. Costs almost nothing.

// scripts/weekly-digest.js
import { Client, GatewayIntentBits } from 'discord.js';
import { db } from './db.js';

const client = new Client({ intents: [GatewayIntentBits.Guilds] });
await client.login(process.env.DISCORD_BOT_TOKEN);

const since = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
const launches = await db.launches.find({
  created_at: { $gte: since },
});

if (launches.length === 0) {
  await client.destroy();
  process.exit(0);
}

const formatted = launches
  .map(l => `• <@${l.user_id}> shipped: ${l.text.slice(0, 200)}`)
  .join('\n');

const channel = await client.channels.fetch(process.env.ANNOUNCEMENTS_CHANNEL_ID);
await channel.send({
  content: `**📦 This week we shipped (${launches.length} launches):**\n\n${formatted}`,
  allowedMentions: { users: [] }, // notify in formatting only, don't ping
});

await client.destroy();
process.exit(0);

Enter fullscreen mode Exit fullscreen mode

On HostingGuru, this runs as an on-demand script triggered by a schedule:

processes:
  weekly-digest:
    type: script
    command: node scripts/weekly-digest.js
    schedule: "0 9 * * 1"  # every Monday at 9:00 UTC

Enter fullscreen mode Exit fullscreen mode

The platform spins up an ephemeral container at the scheduled time, runs the script, captures the output, exits. You pay for ~3 seconds of compute per week. If you've ever fought with Heroku Scheduler, you'll appreciate that the script lives in your repo, version-controlled, with the same env vars as the rest of your app.

Why this architecture matters

The naive temptation is to put all three in one Node process: HTTP server + Discord client + a setInterval for the digest. Don't. Three reasons:

  1. Crashes blast radius. If your slash-command handler throws, the gateway connection survives. If your gateway disconnects mid-deploy, the slash commands keep working. Each process is independently restartable.
  2. Scaling differs. If you have 5,000 slash commands an hour, you scale the web service. The worker stays at 1 instance (you only need one Discord gateway connection per bot). Different processes, different scaling profiles.
  3. Costs differ. The worker burns CPU cycles 24/7 just maintaining a heartbeat. The script runs 3 seconds a week. Putting them on the same dyno is paying always-on prices for a once-a-week task.

This three-process pattern is what you want for any bot or background-heavy service: not just Discord. Slack apps. Telegram bots. Webhook receivers with async fanout. The shape repeats.

What I learned

  1. WebSocket gateway = needs a worker, not a web service. Free web tiers will sleep your bot. Workers don't sleep on platforms that respect the worker primitive.
  2. Slash commands ≠ gateway. They're HTTP, you can host them anywhere, but the 3-second response cap is real. Don't do heavy work inline — log to DB, respond, finish processing async.
  3. Use a real database, not in-memory state. I tried "just use a JSON file" for v0. Workers restart, files vanish, members lost their launch history once. Two days later I wired Postgres.
  4. Scheduled scripts > setInterval. A setInterval in your worker tied to wall-clock time will drift, miss runs during deploys, and double-fire if you scale to 2 instances. A scheduled script run as a separate process is exactly-once, exactly-on-time.
  5. Always reply within 3 seconds. If your slash command handler does anything slow (database query > 1s, external API), respond with a deferred response (type: 5) and follow up later via Discord's webhook.

When this architecture is overkill

If you're building a bot for 5 friends to play a Discord trivia game, run it in one Node process on your laptop. You don't need three processes. You don't need a database. You probably don't need slash commands.

The three-process pattern starts paying off when at least one of these is true:

  • The bot is mission-critical (community would notice if it's down).
  • The bot has > ~50 users sending it traffic.
  • You need scheduled jobs that must run even if the bot crashed yesterday.
  • You're deploying it on a platform where free web tiers sleep.

For ShipTrack at 220 members + weekly digest, all four were true. So the three-process setup paid for itself the first time the worker crashed at 2am and the slash commands kept working through it.

A note on HostingGuru

The reason I'm building on HostingGuru (besides the obvious "I run it" disclosure at the top) is that the three primitives I needed — web service, worker, on-demand scheduled script — are first-class citizens on the platform. Same repo, same env vars, three lines of YAML config. No fighting with Heroku Scheduler vs Heroku dynos vs Heroku one-off heroku run jobs. No spinning up separate ECS task definitions on AWS.

If you're building anything with these three shapes — and most bots, webhook receivers, and background-heavy services have them — Pro tier (€35/mo) gets you 10 services with workers and on-demand scripts included. The free Starter tier supports the web service piece if you want to wire your worker elsewhere.

What's next

ShipTrack v3 is on my todo list. I want to add:

  • An LLM-powered launch summary at the bottom of each digest ("this week's theme: AI productivity tools")
  • A /profile @user command that shows someone's all-time launches
  • A leaderboard of "most active shipper this quarter"

If you've built a Discord bot recently and have hard-won lessons, I'd love to hear them in the comments. Especially the embarrassing v1 stories.


Previous posts in this series:

1. Heroku just went into "sustaining engineering mode." Here are 5 alternatives whose free tier actually doesn't sleep

2. I built my MVP with Claude Code. Now I need to deploy it. Here's what nobody tells you.

3. Your AI app is silently burning $2,000/month and you don't know it.

4. Telegram alerts for any production app — a 5-minute setup.