Wake up to an AI-written news briefing in your inbox every morning, on the topics you choose, written from real freshly-searched articles.100% free, no server, runs entirely on GitHub Actions.
A real briefing. Every headline, summary and link in it was chosen and written by the agent.
It's 8am. Your coffee is still too hot to drink, so you open your inbox, and the only thing worth reading is already there. Three stories that actually matter to you, four sentences each, every source linked and dated. Nothing you were sent yesterday. No feed, no notifications, no twenty tabs you'll never get back to.
While you were asleep, an agent searched the web on your topics, threw out the filler, read what was left and wrote it up. By the time your coffee is drinkable, you're caught up, and you close the tab and get on with your day.
You need two free API keys. No Gmail, no deploy, no account anywhere else.
git clone https://github.com/tballochi/daily-briefing.git
cd daily-briefing
pip install -r requirements.txt
python main.py --setup # asks for your keys and checks each one works
python main.py --dry-run # builds a real briefing, sends nothing
Both keys are free: Groq and
Tavily. Prefer not to use the wizard? Put them in a .env
file yourself as GROQ_API_KEY=
and TAVILY_API_KEY=
.
--dry-run
researches the news, writes the briefing, prints it to your terminal and
saves the rendered email to data/preview.html
. It sends nothing and stores nothing, so you can see exactly what you'd be getting before setting up delivery.
Then edit config.yaml
to your own topics and run it again.
Every morning, an autonomous agent:
Researches. Runs live web searches acrossyourtopics, judges the results, and skips anything it already sent you on a previous day.Writes. Summarises the chosen stories from their real source text. No hallucinated facts, no fabricated links: every URL comes from a real search result.Delivers. Emails you a newspaper-style digest, at the time you choose.
It's a tool-using agent, not a fixed script: it decides what to search, judges what's worth keeping, and searches again if the results are thin.
In the email: your N top stories, each with a factual summary, source and publication date Β· a word of the day Β· a quote of the day. Optionally, one slot is reserved for a focus theme you never want to miss.
One readable file, no code, no secrets:
title: Daily Briefing # shown in the email subject + header
num_articles: 3 # how many stories per morning
topics: # what the agent researches (free text)
- AI & LLMs (GPT, Claude, agents, MCP)
- automation & no-code (n8n, Zapier, Make)
- dev tools & open source
focus: # OPTIONAL: a story that's ALWAYS included
label: shipping / maritime / logistics
priority_query: CMA CGM # searched first; preferred when there's fresh news
keywords: [cma cgm, shipping, maritime, container, freight]
model: openai/gpt-oss-120b # the Groq model the agent thinks with
model_fallbacks: # tried in order if the one above is retired
- openai/gpt-oss-20b
- qwen/qwen3.8-27b
Topics are free text, so point it at finance, climate, football, your industry, whatever
you want to wake up informed about. Don't want a guaranteed story? Delete the focus:
block. No config.yaml
at all? Sensible defaults keep it running.
Nothing stays on. No server, no container, no cron on your laptop.
1. Click ** Use this template** to get your own copy (or fork it). Then clone it and run:
python main.py --setup
The wizard asks for each key, verifies it against the real API before saving, so a wrong
Gmail App Password fails here instead of silently at 8am tomorrow. It clears the inherited
de-duplication history, offers to upload your secrets with the gh
CLI, and prints your pinger URL with your repo already filled in.
2. Add 5 Settings β Secrets and variables β Actions secrets (the wizard can do
this for you if you have the gh
CLI):
| Secret | Where to get it |
|---|---|
GROQ_API_KEY |
|
TAVILY_API_KEY
app.tavily.com, free, 1000 searches/monthGMAIL_ADDRESS
GMAIL_APP_PASSWORD
16-char App Password(needs 2FA).Not your Gmail passwordRECIPIENT_EMAIL
3. Test it now: Actions β Daily Briefing β Run workflow, tick force
.
4. Make it punctual by adding a free external pinger (5 minutes, one time):
Why, and how to set up the free cron pinger
GitHub's own schedule:
cron is unreliable on low-activity repos: it drops most triggers and can fire hours late, so "before 10am" isn't guaranteed by it alone. The fix, still 100% free, is a tiny external cron that calls GitHub at a fixed time. GitHub's own cron stays on as a best-effort backup.
a) Create a GitHub token at new fine-grained token
- Repository access:
Only select repositoriesβ this repo - Permissions β
Actions: Read and write - Copy the
github_pat_...
value
b) Create a free job on cron-job.org, daily at ~08:00 in your timezone:
Method:POST
URL:https://api.github.com/repos/<you>/daily-briefing/actions/workflows/daily-briefing.yml/dispatches
Body:{"ref":"main"}
Headers:Accept: application/vnd.github+json
Authorization: Bearer <your github_pat_...>
X-GitHub-Api-Version: 2022-11-28
A successful call returns HTTP 204. The pinger fires without forcing, so it still passes the morning-window and once-a-day guards, so it can never double-send.
Renamed the repo later? Update the URL by hand; GitHub redirects GETs but not reliably POSTs.
The things this does differently, and what they cost:
No paid step, anywhere |
It runs inside the Groq, Tavily and GitHub Actions free tiers. A daily run uses ~180 of Tavily's 1000 monthly searches. Worth being clear: those are three companies' free tiers, not a guarantee I can make. If one changes, this changes. |
No infrastructure |
No server, no Docker, no database, no always-on process. The trade-off is that you're running on GitHub's schedule and inside their limits. |
An agent, not an RSS pipeline |
It picks its own search queries, judges result quality, and searches again when results are thin, which also means it's slower and less predictable than a feed reader. |
Never repeats a story |
Sent articles are matched by normalised URL and headline, kept in data/history.json and committed back to the repo. No database to run; the trade-off is a daily commit in your history. |
Never double-sends |
A morning-window guard plus a once-a-day guard, so several triggers still produce one email. |
Survives model deprecations |
The model is configurable with a fallback chain. This exists because a hardcoded model was retired by the provider and took the agent down for a day. |
Grounded summaries |
Written only from real source text, and any URL the model didn't actually find in a search result is dropped before it can reach the email. |
Keys stay yours |
They live in your own repo's secrets or your local .env . The agent talks to Groq, Tavily and Gmail's SMTP server, and nothing else. |
What does it cost?
Nothing. A run uses a handful of Groq calls and up to 6 Tavily searches, well inside both free tiers (Tavily gives 1000 searches/month; the briefing uses ~180). GitHub Actions is free for public repos. cron-job.org is free. There is no paid tier to graduate into.
Where do my API keys live? Is anything sent to you?
Your keys live in your GitHub repo's Actions secrets (or your local .env
, which is gitignored). The agent talks only to Groq, Tavily and Gmail's SMTP server. Nothing is sent anywhere else, and the author never sees your keys, topics or briefings.
How do I change the model?
Edit model:
in config.yaml
to any model your Groq key can reach, and list backups
under model_fallbacks:
. You can also override it without editing the file by setting
the GROQ_MODEL
environment variable (or a GROQ_MODEL
GitHub Actions variable). Providers retire models regularly, so check Groq's deprecations page for what's live.
Can I use something other than Gmail?
Yes, with a small edit. email_sender.py
uses plain smtplib
over SSL, so pointing
SMTP_HOST
/ SMTP_PORT
at another provider (Fastmail, Zoho, a company relay) and using that account's credentials works. Gmail is the default only because its App Passwords are free and easy.
Can I change the delivery time?
Yes. The time is set by your cron-job.org job; the workflow only enforces a
06:00β11:00 Europe/Paris safety window so a stray trigger can't email you at night.
To move outside that window, edit the hour list in the gate step of
.github/workflows/daily-briefing.yml
(and TIMEZONE
in scheduler.py
for local runs).
What if it fails one morning?
It retries once after two minutes. If that also fails you get a short "couldn't send today" email explaining why, the full traceback goes to the Actions log, and the run is marked failed, so a broken morning is red in the Actions tab rather than a green check. The next morning's run is unaffected.
Can I get more than 3 stories, or several briefings a day?
Set num_articles:
to whatever you like. One briefing per day is enforced by design, because the de-duplication history keys on the date, so a second daily send would need that guard changed.
python main.py --setup # guided setup (or copy .env.example by hand)
python main.py --dry-run # preview only, sends nothing
python main.py --now # build & send one briefing now
python main.py # run the scheduler (daily at 09:00 Europe/Paris)
pip install -r requirements-dev.txt
pytest -q
Mocked network, no API keys needed, runs in under a second. Covers the model fallback chain, config defaults, de-duplication and both send guards. CI runs it on every push.
| Tool | Role |
|---|---|
| Python 3.11+ | Core language. 5 dependencies, no framework |
Groq (openai/gpt-oss-120b ) |
|
| The agent's brain. Configurable, with fallbacks | |
| Tavily | Real-time news search |
| GitHub Actions | Runtime for the daily job |
| cron-job.org | External pinger, so it fires on time |
daily-briefing/
βββ config.yaml # your preferences: title, topics, focus, model
βββ config.py # loads/validates config.yaml, checks required secrets
βββ main.py # entry point (--setup / --dry-run / --now)
βββ setup_wizard.py # guided setup, verifies every key
βββ agent.py # the agent: research loop, writing, HTML rendering
βββ email_sender.py # Gmail delivery
βββ scheduler.py # the briefing jobs + local daily scheduler
βββ history.py # remembers sent articles (no repeats)
βββ data/history.json # the de-duplication memory
βββ docs/ # screenshot + sample briefing
βββ tests/ # pytest suite (no network)
βββ .github/workflows/ # daily-briefing.yml (the morning run) + ci.yml
Issues and PRs welcome. See CONTRIBUTING.md.
MIT. See LICENSE.
Built by TimotΓ© Ballochi.