Why this exists
I wanted the first thing read every morning to be a single calm signal, not the news, not group chats, not Slack. Something that answers two questions cleanly: what’s the workout today, and what is it going to feel like outside.
Off-the-shelf “habit” apps drown that in dashboards and streaks. The brief is the opposite: one short email. No app to open.
What it does
Every day at 06:30, an inbox gets a short, warm note that includes:
- The day’s workout (rotating push / pull / legs / pull / legs over the week, with a “light” variant available)
- The Miami forecast: high, low, rain probability, a one-line take (“breezy”, “muggy by noon”)
- A small hand-written touch: a greeting, a sentence of encouragement, a thought to carry into the day
It’s intentionally one screen tall on a phone. If you have to scroll, the brief failed.
How it works
The flow is simple. That is the point.
- Trigger: A scheduled GitHub Action wakes a Python script at 06:30 local time
- Fetch: Two parallel API calls: OpenWeatherMap for the day’s forecast, and a lookup against a local table of workouts keyed by day-of-week
- Compose: Claude is given the raw signals and a tight prompt that fixes tone, length, and structure. It returns a short, friendly note
- Send: A plain SMTP send to the recipient’s inbox. No service in the middle, no third-party form
A few choices kept this small: email rather than an app (no install, no notification permission), a single LLM call rather than chained calls, and a hard length cap in the prompt because the model otherwise drifts toward over-helpful (“here are seven tips for your push day”). End-to-end runtime is under five seconds, which means cost is effectively the Claude token spend (low single-digit cents per day) plus the free tier of OpenWeatherMap.
What’s next
- Switch to a single, idempotent endpoint instead of GitHub Actions cron. Cron is reliable but the cold-start path is opaque when something fails; a logged endpoint would tell me why the 06:30 brief didn’t show up
- Add a feedback loop. A one-tap reply (“less spicy”, “rest day”) would let the brief learn without me opening the code
- Move the workout table out of the script. It’s a small spreadsheet’s worth of data and it doesn’t belong in source code
What I learned
The lesson was about scope. Early versions used Claude for routing (picking the workout, picking the tone, deciding what to skip) and every failure was a mystery. Now the script handles those decisions and Claude only does the writing. One prompt, one paragraph. That’s the version that’s still running.
Status
Shipped. Running daily. The next iteration is mostly about tone and resilience, not new features.