Serverless Discord Bot • AWS • Python

Hydration Station

A serverless Discord bot that nudges a server to drink water and lets members log their intake with slash commands. It runs fully serverless on AWS (Lambda, DynamoDB, and EventBridge), so it stays online around the clock for effectively $0 a month.

Status Live on AWS

Fully deployed and running within the AWS always-free tier at roughly $0/month, supporting multi-user servers.

Project Summary

Project Overview

Purpose

Reminds a channel to hydrate on a configurable schedule and tracks each person's intake against their own daily goals.

Problem

Staying hydrated is easy to forget, and existing reminder bots either need an always-on server (a recurring cost) or are one-size-fits-all.

Approach

A self-hostable, fully serverless bot that costs nothing to keep online and needs no server to manage.

Feature System

Core Features

Logging01

Flexible Intake Logging

  • Log in cups, bottles (custom size), ml, or oz
  • Quick correction with /undo
  • Timezone-aware day boundaries
Goals02

Personal Daily Goals

  • Per-person daily targets
  • Per-weekday goals (for example, higher on workout days)
  • Progress tracked against each goal
Reminders03

Configurable Reminders

  • Every N hours within a set window
  • Or fixed times of day
  • Timezone-aware scheduling
Commands04

Slash Commands

  • /today and /progress
  • /history and /leaderboard
  • /undo
Modes05

Individual or Team

  • Per-person tracking
  • Shared team-goal mode
  • Admin-gated switch between modes
Nudges06

Smart Reminders

  • @mentions only people behind their goal
  • Shows each person's progress
  • Skips anyone already done
Architecture

How It Works

Commands01

Interactions Lambda

Discord calls a public Lambda Function URL on every slash command. The function verifies the request signature, reads and writes DynamoDB, and replies within Discord's roughly 3-second window.

Scheduling02

Reminder Lambda

A single EventBridge schedule fires every 15 minutes. The function checks each server's configured schedule and posts a reminder only when one is actually due.

Storage03

Single-Table DynamoDB

One table holds server config, per-user profiles and goals, atomic daily rollup counters, and individual log entries.

Endpoint04

No API Gateway

A public Lambda Function URL serves the endpoint directly, keeping the stack to the fewest possible moving parts.

Tech Stack

Tools + Build

Python 3.12Standard Library OnlyAWS LambdaLambda Function URLsAmazon DynamoDBAmazon EventBridgeDiscord Interactions APIDiscord REST APIEd25519 VerificationAWS IAMboto3zoneinfourllibServerless Architecture
Engineering + Design

Decisions Worth Calling Out

Cost01

Serverless for $0

An always-on gateway bot needs a process running 24/7, which recurs as a cost. Modeling the bot as HTTP interactions plus a scheduled function keeps every piece inside AWS's always-free tier, so it runs indefinitely for free.

Footprint02

Zero Third-Party Dependencies

Everything uses the Python standard library plus AWS's built-in boto3. No dependency packaging, no binary-wheel mismatches, and the code pastes straight into the Lambda console. That even meant hand-writing a pure-Python Ed25519 verifier instead of pulling in PyNaCl.

Data03

Atomic Counters

Daily totals use atomic increment updates, avoiding read-modify-write races. Every logged drink updates both a per-user rollup and a server-wide rollup, so the bot can switch between individual and shared team modes without ever losing history.

Scheduling04

One Static Schedule

Instead of creating and deleting schedules when users change settings, a single 15-minute trigger runs the Lambda, which decides in code what's due (interval window or fixed times). Simpler, cheaper, and stateless.

Security05

Security by Verification

The Function URL is public, but every request is cryptographically verified against the app's Ed25519 public key before processing (the standard Discord model), with least-privilege IAM scoping each Lambda to exactly the actions it needs.

UX06

Thoughtful UX

Drink logs post publicly as a social nudge, while private replies auto-delete after about 25 seconds (via an async self-invoke, since Discord has no native message TTL). Reminders mention only the people who are behind, and show their progress.

Problem Solving

Notable Challenge

Symptom01

A Mysterious Timeout

The Discord endpoint kept failing verification with a vague error. Logs showed the Lambda hitting a 10-second timeout. The first pass of the pure-Python Ed25519 verifier used affine coordinates, doing a modular inverse on every step. Fine on a laptop, but on a 128 MB Lambda (a sliver of a vCPU) a single signature took over 10 seconds.

Fix02

A ~1000x Speedup

Rewriting the verifier around the RFC 8032 extended-coordinate algorithm removed the per-step inversion and brought verification down to about 5 milliseconds, validated against the official RFC 8032 test vectors. A clean reminder that algorithmic complexity matters most where CPU is scarce.

Engineering + Quality

Practices Worth Highlighting

Testing01

Tested Without the Cloud

An in-memory boto3 and DynamoDB stub runs the real handlers end to end offline, and the crypto is validated against RFC 8032 vectors, so the whole thing is testable with no cloud access.

IAM02

Least-Privilege IAM

Each function is scoped to its specific DynamoDB table actions, plus a single self-invoke permission for the interactions function and nothing more.

Resilience03

Graceful Degradation

Command dispatch always returns a reply, and optional features like auto-delete fail silently rather than breaking the command.

Keep Exploring

More projects like this

← Back to all projects