A personal daily digest that aggregates news, weather, sports, entertainment, and health data into a single mobile-friendly HTML email. A Python orchestrator pulls from seven independent sources, an LLM summarises and validates the content, and it lands in my inbox every morning โ running locally or as a scheduled AWS Lambda.
Every morning the app assembles a personal briefing and emails it as a single, clean HTML message. Each section comes from a dedicated module so sources can be added or swapped without touching the others:
python main.py
or as an AWS Lambda triggered by EventBridge on a daily schedule โ with a lambda_handler() entry
point and S3 holding state between runs.
main.py is the orchestrator: it calls each source module, normalises and validates the results,
renders the email, and sends it. Modules are independent and fail soft, so one flaky API doesn't sink the
whole digest.
flowchart TD
EB[EventBridge
daily schedule] --> M
M[main.py
orchestrator]
M --> N[news.py
Serper API]
M --> W[weather.py
NWS + Open-Meteo]
M --> S[sports.py
CricAPI + RSS]
M --> R[rugby.py
AI agent + web search]
M --> MV[movies.py
Tavily API]
M --> O[oura_pull.py
Oura API]
O --> SC[smart_cache.py
S3-backed cache]
N --> AI{{OpenAI
summarise + validate}}
W --> AI
S --> AI
R --> AI
MV --> AI
O --> AI
AI --> H[render HTML email]
H --> G[Gmail SMTP send]
classDef llm fill:#fef3c7,stroke:#d97706,color:#000
class AI llm
Seven independent source modules fan in to the orchestrator; OpenAI cleans and validates before send.
Each module owns one source and one API, returning ready-to-render content to the orchestrator.
| Module | Source / API | Provides |
|---|---|---|
| news.py | Serper (Google search) | South Africa & Amazon news |
| weather.py | NWS + Open-Meteo | US & international forecasts |
| sports.py | CricAPI + RSS | SA cricket scores + sports feeds |
| rugby.py | AI agent + web search | Springboks rugby news |
| movies.py | Tavily search | Top movies & TV shows |
| oura_pull.py | Oura API | Sleep, readiness & activity |
| smart_cache.py | S3 / local JSON | Caches Oura analysis to cut API cost |
main.py coordinates fetching, sanitising, formatting, validation, and sending โ with token accounting on every OpenAI call.
A lambda_handler() entry point runs the same code in the cloud; Docker-built deploy packages ship it.
A scheduled rule fires the Lambda once a day so the digest arrives without any manual trigger.
Summarises noisy source content into clean bullets and optionally validates the final email's formatting.
Holds state between Lambda runs โ cached Oura metrics, analysis, and API usage counters.
Pulls 90 days of health data, "heals" gaps caused by Oura API quirks, and caches AI analysis to reduce cost.
Assembles a MIME multipart message and delivers the mobile-friendly HTML email to the inbox.
JSON schemas constrain the LLM's news output so rendering stays predictable and safe to template.
EventBridge triggers the Lambda (or a local cron / manual run) once a day to start the digest build.
The orchestrator calls each source module โ news, weather, sports, rugby, movies, Oura โ independently, so a single failing API degrades gracefully instead of breaking the run.
OpenAI condenses noisy content into clean bullets against JSON schemas; URL and text sanitisers strip tracking params and de-dupe, all under per-run token accounting.
Oura history is back-filled around the API's single-day quirk and cached in S3 so repeat analysis doesn't re-spend tokens.
All sections are assembled into a mobile-friendly HTML template and packed into a MIME multipart message โ with an optional AI pass that validates the formatting.
The digest goes out via Gmail SMTP and lands in the inbox โ one concise briefing instead of a morning of scattered reading.