{"id":50767594,"url":"https://github.com/roymcfarland/agentic-daily-briefing","last_synced_at":"2026-06-11T15:02:04.651Z","repository":{"id":361280670,"uuid":"1200109875","full_name":"roymcfarland/agentic-daily-briefing","owner":"roymcfarland","description":"Agentic workflow that sends a daily morning briefing email. Pulls task state from Workflow Blueprint API. Aggregates live user-defined research, including one asymmetric-upside slot. Removes duplicates and low-signal items. Ranks for implication and decision relevance. Ends email with one thing to watch, one thing to ignore and one contrarian take","archived":false,"fork":false,"pushed_at":"2026-06-07T05:50:33.000Z","size":329,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-07T07:11:54.571Z","etag":null,"topics":["agentic-ai","agentic-workflow","daily-briefing","llm","news-aggregator","nextjs","pesrsonal-project","resend","rss","typescript","vercel","vercel-cron"],"latest_commit_sha":null,"homepage":"https://www.roymcfarland.news","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/roymcfarland.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-04-03T03:38:51.000Z","updated_at":"2026-06-07T05:50:35.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/roymcfarland/agentic-daily-briefing","commit_stats":null,"previous_names":["roymcfarland/agentic-daily-briefing"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/roymcfarland/agentic-daily-briefing","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roymcfarland%2Fagentic-daily-briefing","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roymcfarland%2Fagentic-daily-briefing/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roymcfarland%2Fagentic-daily-briefing/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roymcfarland%2Fagentic-daily-briefing/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/roymcfarland","download_url":"https://codeload.github.com/roymcfarland/agentic-daily-briefing/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roymcfarland%2Fagentic-daily-briefing/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34204197,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-11T02:00:06.485Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agentic-ai","agentic-workflow","daily-briefing","llm","news-aggregator","nextjs","pesrsonal-project","resend","rss","typescript","vercel","vercel-cron"],"created_at":"2026-06-11T15:02:03.656Z","updated_at":"2026-06-11T15:02:04.643Z","avatar_url":"https://github.com/roymcfarland.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Daily Morning Brief\n\nNext.js App Router project for a daily morning email briefing, designed for Vercel deployment and Vercel Cron.\n\n## About this repo\n\nThis is the source of a personal production system I use every morning. It runs on Vercel Cron, fetches my open tasks from a separate internal API I built ([Workflow Blueprint](https://www.workflowblueprint.io)), pulls live news from a curated set of Google News RSS feeds, ranks everything for decision relevance, and emails me one digest at 6:00 AM Mountain. It is intentionally hardcoded for one reader and one set of beats; it is not a multi-user product.\n\nThe public landing page lives at [roymcfarland.news](https://www.roymcfarland.news).\n\nIf you want to understand the architectural reasoning, the constraints, and the rules a code-review agent enforces on every PR, read [`PROJECT.md`](./PROJECT.md) before the code itself. It is the authoritative source of truth for the project and gives more context than the code does on its own.\n\n## What it does\n\n- Sends a daily morning briefing email with [Resend](https://resend.com/)\n- Runs from `/api/cron/morning-brief`\n- Pulls task state from the Workflow Blueprint v1 API via `getDailySummary` only\n- Covers Personal and Brightline Labs as the two task areas\n- Pulls live research from Google News RSS across AI, Markets, Business, CPG \u0026 Startups, Chicago, Colorado, and one asymmetric-upside area\n- Removes duplicates and low-signal items\n- Ranks for implication and decision relevance\n- Ends with one thing to watch, one thing to ignore, and one contrarian take\n\n## Project structure\n\n- `app/api/cron/morning-brief/route.ts`: cron entrypoint and authorization\n- `lib/blueprint/generated/client.ts`: generated Workflow Blueprint API client\n- `openapi/blueprint.openapi.json`: source schema for the generated client\n- `lib/briefing/pipeline.ts`: data collection, ranking, and digest assembly\n- `lib/briefing/formatter.ts`: HTML and text email rendering\n- `vercel.json`: UTC cron schedule for one delivery per day\n\n## Why one Vercel cron schedule\n\nVercel Cron uses UTC schedules. This project is configured with one daily cron at `0 12 * * *`.\n\nThat means:\n\n1. It sends once per day, not twice.\n2. On April 4, 2026, that schedule maps to `6:00 AM America/Denver`.\n3. After daylight saving time ends, it will map to `5:00 AM America/Denver` unless you adjust the UTC schedule.\n\n## Environment variables\n\nCopy `.env.example` to `.env.local` for local development and configure the same values in Vercel production:\n\n```bash\ncp .env.example .env.local\n```\n\nRequired values:\n\n- `BLUEPRINT_API_BASE_URL`\n- `EXTERNAL_API_KEY`\n- `RESEND_API_KEY`\n- `BRIEFING_FROM_EMAIL`\n- `BRIEFING_TO_EMAILS`\n- `CRON_SECRET`\n- `BRIEFING_IDEMPOTENCY_REDIS_REST_URL`\n- `BRIEFING_IDEMPOTENCY_REDIS_REST_TOKEN`\n\nOptional values:\n\n- `BLUEPRINT_TIMEOUT_MS`\n- `BRIEFING_SUBJECT_PREFIX`\n- `BRIEFING_MAX_ITEMS`\n- `BRIEFING_IDEMPOTENCY_SENT_TTL_SECONDS`\n- `BRIEFING_IDEMPOTENCY_LOCK_TTL_SECONDS`\n\nThe idempotency variables can use either the project-specific names above or Vercel KV's `KV_REST_API_URL` and `KV_REST_API_TOKEN` aliases. Production sends fail closed without a persistent idempotency backend so cron retries cannot double-send.\n\n## Local development\n\nInstall dependencies:\n\n```bash\nnpm install\n```\n\nGenerate the upstream API client from the OpenAPI schema:\n\n```bash\nnpm run generate:blueprint\n```\n\nStart the app:\n\n```bash\nnpm run dev\n```\n\nManually trigger the cron route in development:\n\n```bash\ncurl -H \"Authorization: Bearer $CRON_SECRET\" http://localhost:3000/api/cron/morning-brief\n```\n\nPreview the assembled briefing without sending email:\n\n```bash\ncurl -H \"Authorization: Bearer $CRON_SECRET\" \"http://localhost:3000/api/cron/morning-brief?preview=1\"\n```\n\nProduction domain:\n\n```bash\ncurl -H \"Authorization: Bearer $CRON_SECRET\" \"https://www.roymcfarland.news/api/cron/morning-brief?preview=1\"\n```\n\n## What I'd do differently if I started over\n\nA few things I would change with the benefit of hindsight, kept here for honesty rather than polish:\n\n1. **The ranker is mostly hand-tuned regex and source weights.** It works well enough that I read the brief every morning, but it is fragile to topic drift. A small evaluation harness — a few dozen labeled stories per beat, scored against the current ranker — would make changes safer and let me swap the heuristic layer for a small LLM judge without guessing whether quality regressed.\n2. **Idempotency lives in two places.** The route uses Redis/Upstash to lock the day, and Resend's own idempotency key is passed as a backstop. That belt-and-suspenders design has saved me from double sends, but it also means the truth about \"did today's brief send\" is split across two systems. A single durable record with a clear state machine would be cleaner.\n3. **No structured observability.** Failures show up in Vercel function logs and (sometimes) in the email itself as a warning banner. For a tool I depend on daily, I should have a tiny `/api/health` summary plus a once-a-week digest of partial failures, rather than relying on me noticing a missing brief.\n4. **The landing page and the cron job share a Next.js app for convenience.** That is fine today, but if the briefing logic ever needed a heavier runtime (e.g., a real LLM judge), the right move would be to split the cron into its own service so the marketing site stays static and cheap.\n5. **`PROJECT.md` was added late.** The verifier rules and non-goals would have prevented at least three of the dumber refactors I made early on. If I were starting again, I would write `PROJECT.md` before the first commit, not after the tenth.\n\n## License\n\nThis project is licensed under the PolyForm Noncommercial License 1.0.0. See the [LICENSE](LICENSE) file for details. Commercial use is strictly prohibited without express written permission from Roy McFarland.\n\n## Deploy to Vercel\n\n1. Import the repo into Vercel.\n2. Set the environment variables from `.env.example`.\n3. Ensure your sender domain is verified in Resend.\n4. Set `CRON_SECRET` in Vercel and call the route with that shared secret.\n5. Point `roymcfarland.news` and `www.roymcfarland.news` at the Vercel project.\n6. Deploy. Vercel will pick up `vercel.json` and create the cron job.\n\n## Upstream API client generation\n\nThe generated client is intentionally scoped to `getDailySummary`, which is the only upstream API method used by this app today.\n\nIf the upstream schema changes:\n\n1. Update `openapi/blueprint.openapi.json`\n2. Run `npm run generate:blueprint`\n3. Commit the regenerated `lib/blueprint/generated/client.ts`\n\n## Testing\n\nRun the formatter and ranker tests with:\n\n```bash\nnpm test\n```\n\n## Production notes\n\n- The route uses `getDailySummary` only and does not rely on any upstream dashboard behavior.\n- Research is gathered live at send time from public RSS search results, then deduped and ranked.\n- If a feed fails, the pipeline continues with the remaining sources.\n- The route returns JSON so Vercel Cron logs stay readable.\n- `preview=1` can be used on an authenticated request to inspect the assembled digest without sending email.\n- Sent emails use a stable Chicago-date idempotency key in Redis/Upstash and Resend, so duplicate cron retries for the same day return the persisted send record instead of sending again.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Froymcfarland%2Fagentic-daily-briefing","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Froymcfarland%2Fagentic-daily-briefing","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Froymcfarland%2Fagentic-daily-briefing/lists"}