Daily Monkey → Slack Bot
A 60-line toy automation with grown-up discipline: retry logic, exponential backoff, structured logging, and a runbook. Because even small things should survive without me.
Context
Small automations usually die at 2 a.m. on a Saturday, because they were written under the assumption that they wouldn't. Weekend downtime, upstream API flakes, a forgotten expired token: all of it hits the thing you never documented, and you end up rebuilding from memory.
I use a lot of small automations. This one is the smallest. It posts a random MonkeyUser developer comic to a Slack channel every weekday morning. There is no business case for it. I built it because if I can't get the small things disciplined, I have no business claiming I can get the big things disciplined.
Problem
Any shipped automation has the same shape of failure modes:
- Upstream service is slow or flaky.
- Credentials rotate or expire.
- The code works in dev and breaks on the runner because of some environment difference.
- Nobody else knows how to fix it.
- It fails silently and you only notice three months later when someone points it out.
Most people skip the discipline because the stakes are low. I skipped the stakes instead.
Shape of the solution
A GitHub Actions workflow on a cron schedule, calling a Python script, with secrets in GitHub's vault and a webhook to Slack. Two files plus a README. Everything in a public repo.
flowchart LR Cron[GitHub Actions cron<br/>weekdays 09:00 UTC] --> Py[post_comic.py] Py -->|GET random| API[MonkeyUser API] API -->|JSON| Py Py -->|POST blocks| Slack[Slack webhook] Py -.retry 3x exp backoff.-> API
Key design decisions
1. GitHub Actions over anything hosted. Free. Version-controlled. Logs searchable in the UI. No AWS console to miss. No cron job hiding on a laptop. The trade-off (a 60-minute runtime limit that doesn't matter for a ten-second script) is a non-issue.
2. Retry with exponential backoff, not just retry. The upstream is a side-project API hosted on Vercel's free tier. Flaky by design. Three retries with exponential backoff (1s, 2s, 4s) handles 99% of transient failures. A hard fail after that surfaces as a red GitHub Actions run, which I'll actually notice.
3. Structured logging, not print statements.
Every step emits a one-line JSON log with timestamp, level, and event. When something breaks three months from now, the logs are scannable in the Actions output without ceremony.
4. Slack Blocks, not raw text. A plain-text message would work. Slack Blocks adds a title, an image, and a source link that render as a proper card. Same amount of code, meaningfully better UX for the channel.
5. Field validation on the upstream response. If the API returns a malformed payload (missing image URL, broken metadata), the script fails loudly rather than posting a garbage message. The validation is five lines. It's saved me twice.
Proof
The runbook lives next to the code:
The Python loop is deliberately boring:
def fetch_comic(max_retries: int = 3) -> dict:
for attempt in range(max_retries):
try:
r = requests.get(API_URL, timeout=10)
r.raise_for_status()
data = r.json()
validate(data) # raises on missing fields
return data
except (requests.RequestException, ValidationError) as e:
backoff = 2 ** attempt
log("retry", attempt=attempt, backoff=backoff, error=str(e))
time.sleep(backoff)
raise RuntimeError("fetch_comic failed after retries")That's the core. Everything else is wiring.
What I'd do with a team
Every team has a dozen of these. A daily digest, a reminder, a forgotten cron job, a status check. They exist because someone cared enough to build them and never got around to documenting them. Then that person leaves.
The discipline that keeps a 60-line script alive is the same discipline that keeps a 60,000-line system alive: a runbook next to the code, secrets in a vault, retries where the upstream is flaky, structured logs, and a named owner. At a team level, the change is that "named owner" becomes "named owners plural, with at least one who isn't me."
If the automation can't survive without me, it isn't really shipped. It's a pet. The difference matters at every scale.