Skip to content

Building a Slack Poker Bot with Zero Server Costs

Posted on:February 18, 2026

How I built a full-featured Texas Hold’em bot using only free cloud resources


The Problem

I wanted to build a poker bot for my friends’ Slack workspace. Nothing fancy; just a way to play a few hands during breaks without leaving Slack. But I had a few hard constraints:

The answer turned out to be a fully serverless stack: Vercel Edge Functions for compute, Upstash Redis for state, and GitHub Actions for CI/CD.


The Architecture

At a high level, the bot works like this:

  1. A player types /poker start in Slack.
  2. Slack sends a webhook to a Vercel Edge Function.
  3. The function reads and updates game state from Redis.
  4. The bot posts an interactive message back to the channel with action buttons (Fold, Call, Raise).
  5. Players click buttons, which trigger more webhooks, and the cycle repeats.

That’s it. No persistent server. No background process. Just functions that wake up, do their job, and go back to sleep.

Why This Stack?

ComponentFree TierWhy It Works
Vercel100 GB / month bandwidthEdge functions start in ~50ms; well within Slack’s 3s response window
Upstash Redis500 K commands/ monthGame state is tiny; TTLs handle cleanup automatically
GitHub Actions2,000 min/monthPlenty for running tests on every PR
Slack APIUnlimited messagesBot messages are free

Building the Bot

Handling Slack Commands

Slack sends a POST request to your server whenever someone types a slash command. The tricky part: you have 3 seconds to respond, or Slack shows an error to the user.

Edge Functions solve this elegantly. They cold-start in ~50ms, leaving plenty of headroom for game logic. The handler verifies the request signature (Slack signs every request with HMAC-SHA256), parses the command, and dispatches to the right handler; all well within the deadline.

Every command goes through the same pipeline: rate-limit check → signature verification → command dispatch. This keeps things secure and predictable.

State Management with Redis

Serverless functions are stateless by design, so all game state lives in Redis. Each game is stored as a JSON blob with a 24-hour TTL; if a game goes stale, Redis cleans it up automatically. Player bankrolls persist for 30 days.

The key insight here is that poker game state is small. A full game with 8 players, all their cards, the community cards, pot sizes, and betting history fits comfortably under 10KB. Redis is overkill in terms of power, but it’s perfect in terms of simplicity and cost.

One thing I had to be careful about: race conditions. When two players click buttons at nearly the same time, both requests might try to read and write the same game state simultaneously. The solution was a simple Redis-based locking mechanism; acquire a lock, read the game, make changes, write back, release the lock.

Interactive Buttons

This is what makes the bot feel like a real game rather than a command-line tool. Slack’s Block Kit lets you attach buttons to messages, and each button click sends a new webhook to your server.

When a player clicks “Raise”, the bot:

  1. Receives the action payload from Slack
  2. Loads the game state from Redis
  3. Validates the action (is it this player’s turn? do they have enough chips?)
  4. Updates the game state
  5. Posts an updated message to the channel showing the new game state

The whole round-trip takes under a second.

Scheduled Cleanup

One underrated feature of Vercel is Cron Jobs; you can schedule serverless functions to run on a schedule, just like a traditional cron job.

I use two:

No background process needed. The functions just run, do their thing, and disappear.


The Fun Stuff

The Wall of Shame

If you go broke (under 20 chips), you can beg for more; but there’s a catch. You have to write a public message explaining yourself, and it gets posted to the channel for everyone to see. In exchange, you get 500 chips back.

The bot keeps a persistent, paginated “Wall of Shame” in Redis so your humiliation lives on even after the bot refreshes the data.

AI-Powered Feature Requests

This is the part I’m most proud of (I’m addicted to building this everywhere now; see Building a Bug-to-PR Pipeline for Kotoba). Users can submit feature requests directly from Slack with /poker feature "Add support for Omaha poker". The bot creates a GitHub issue automatically.

If the user is listed in CONTRIBUTORS.md, the bot goes one step further: it comments /oc on the issue, which triggers a GitHub Actions workflow that runs opencode — an AI coding agent — to actually implement the feature and open a PR.

The flow looks like this:

/poker feature "Add hand history"

GitHub Issue #15 created

User is a contributor → comment "/oc"

GitHub Actions triggers opencode

PR #16 opened with implementation

Deployment

Getting this running is straightforward:

  1. Create a Slack app at api.slack.com/apps. You’ll need a few OAuth scopes: chat:write, commands, and im:write for sending hole cards privately.

  2. Deploy to Vercel. Connect your GitHub repo, add Upstash Redis through the Vercel dashboard (it auto-provisions the connection), and add your Slack credentials as environment variables.

  3. Point Slack at your Vercel URL. Update the slash command and interactive components URLs in your Slack app settings.

  4. Test locally with ngrok. Run npm run dev and use ngrok to expose your local server to Slack’s webhooks. This makes iteration much faster than deploying for every change.


Lessons Learned

Edge Functions are a great fit for Slack bots. The cold start time is negligible, and the 10-second execution limit is more than enough for game logic. If you’re building anything that responds to webhooks, this pattern works well.

Redis TTLs are underrated. Instead of writing cleanup jobs for everything, just set a TTL and let Redis handle it. Game state expires after 24 hours. Sessions expire after an hour. It’s one less thing to think about.

Rate limiting is non-negotiable. Without it, a single user can spam commands and exhaust your free tier limits in minutes. A simple Redis-based rate limiter (increment a counter, set an expiry) is enough to protect against this.

Test with real Slack webhooks early. The Slack API has some quirks — payload formats, response timing, ephemeral vs. in-channel messages — that you won’t discover until you’re actually sending real requests. ngrok makes this easy.


Wrapping Up

Serverless isn’t just for simple webhooks. With Redis for state and a bit of careful design around the stateless execution model, you can build surprisingly complex interactive applications that cost nothing to run.

The full source code is soon to be open-sourced on GitHub. If you build something with it, I’d love to hear about it.


Happy building! 🚀