Build an AI News Digest Bot
Automate a daily AI-curated news digest that generates while you sleep.
Introduction
30 Second Summary
Staying on top of the news is exhausting. By the time you scroll through dozens of headlines every morning, half your coffee is cold and you still feel behind.
In this project, you will build a Python bot that fetches live news, uses AI to pick the five best stories and write summaries, then saves everything as a styled digest you can read in minutes.
What You'll Build
Picture waking up to a polished news digest already sitting in your project folder, curated overnight by AI from the latest headlines across technology, business, and science.
By the end of this project, you'll have:
- A live news bot that pulls headlines from Google News, sends them to an AI model for analysis, and returns the top 5 stories with AI-written summaries.
- A styled HTML digest that looks like a professional newsletter, ready to copy into an email or blog post.
- An automated daily schedule that runs the bot while you sleep so a fresh digest is waiting for you every morning.
- Secret Mission: A "Top Pick" deep-dive feature where the AI selects the single most important story and writes a detailed analysis.
Are there any prerequisites?
This project assumes you have basic Python knowledge. You should also have VS Code and Python already installed on a Windows computer.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters.
Set Up Your Tools
Your AI news digest bot needs two things before it can do any work: access to an AI model that can read and curate headlines, and the right Python libraries for fetching news feeds.
In this step, you will create a free Groq account for AI access, install the Python packages your project depends on, and connect everything by setting up your API key.
In this step, get ready to:
- Sign up for a free Groq account and create an API key.
- Install the groq and feedparser Python packages.
- Set your API key as an environment variable and verify the full setup.
Sign up for Groq and get your API key
Groq is the platform that gives your bot access to the Llama 3.3 70B AI model. The free tier includes 1,000 requests per day, which is more than enough for a daily digest bot.
- Open your browser and go to console.groq.com.
- Click Sign Up and create a free account. No credit card is required.
- Verify your email if prompted, then sign in to the GroqCloud dashboard.
- Click API Keys in the left sidebar.
- Click Create API Key.
- Give your key a name like news-digest and click Submit.
- Copy the key that appears and paste it into a safe place like a notes app or a text file on your Desktop.
What is Groq?
Groq is a cloud platform that hosts large AI models and lets you access them through a simple API. Your bot will send news headlines to Groq's servers, where the llama-3.3-70b-versatile model analyzes them and returns curated summaries.
Your key is shown only once
Once you close the API key dialog, the key is masked and cannot be retrieved. If you lose it, go back to the API Keys page, delete the old key, and create a new one.
Install the Python packages
Your bot depends on two Python packages that do not come pre-installed. You need them before writing any code.
- Install both packages by running this command in your VS Code terminal:
pip install groq==1.6.0 feedparser==6.0.14
What do these packages do?
- groq is the official Python SDK for the Groq API. It handles authentication and sends your requests to the AI model.
- feedparser is a library that parses RSS feeds. It turns raw XML from news sources into clean Python dictionaries you can work with.
You should see output ending with "Successfully installed" followed by the package names. If you see "Requirement already satisfied", the packages are already installed and ready to go.
Seeing an error during install?
- Make sure you are running the command in your VS Code terminal, not inside a Python interpreter. Your prompt should show a folder path, not >>>.
- If you see "pip is not recognized", try running python -m pip install groq==1.6.0 feedparser==6.0.14 instead.
- Both packages require Python 3.10 or higher. Run python --version to check yours. If your version is below 3.10, you will need to upgrade Python before continuing.
I'm getting an error when installing the Python packages for my news digest bot.
Set up your API key and verify the setup
Your Python script will need your API key to call Groq. Storing it as an environment variable keeps sensitive values out of your code files.
What is an environment variable?
An environment variable is a named value stored in your terminal session that programs can read. By storing your API key this way, your Python script can access the key at runtime without it ever appearing in your source code.
- Set your API key as an environment variable by running this command. Replace your-api-key-here with the actual key you copied earlier:
set GROQ_API_KEY=your-api-key-here
Using PowerShell instead of Command Prompt?
If your VS Code terminal prompt starts with PS, you are in PowerShell. Run this command instead:
$env:GROQ_API_KEY="your-api-key-here"
- Test that your API key is valid by running this command:
python -c "import os; from groq import Groq; client = Groq(api_key=os.environ.get('GROQ_API_KEY')); response = client.chat.completions.create(messages=[{'role': 'user', 'content': 'Say hello'}], model='llama-3.3-70b-versatile'); print(response.choices[0].message.content)"
You should see a short greeting from the AI model. This confirms your API key is valid and Groq is responding to requests.
Seeing an authentication error?
- Double check that you pasted your API key without any extra spaces or trailing characters.
- Make sure you ran the set (or $env:) command in the same terminal session. Opening a new terminal starts a fresh session without your variable.
- If the key was lost, go back to console.groq.com/keys, create a new API key, and set the environment variable again with the new value.
My Groq API key test is failing with an authentication or connection error.
Before you run the final check, what do you think it will print?
- Run the final setup verification:
python -c "from groq import Groq; import feedparser; print('Ready!')"
You should see Ready! printed in your terminal. This confirms both packages are installed and importable.
Not seeing Ready! in your terminal?
- If you see ModuleNotFoundError, the package it names was not installed. Run the pip install command from the previous substep again.
- Make sure you are not inside a virtual environment that does not have the packages. Your terminal prompt should not show a (venv) prefix unless you installed the packages inside that environment.
I'm not seeing Ready! when I verify my news digest bot setup.
Your tools are installed and your API key is connected. Next up, you will fetch live news headlines from Google News and see real data flowing through your bot.
Fetch Live News Headlines
Your tools are installed and your Groq API key is ready to go. Now you need something for the AI to actually work with.
Before any AI can curate or summarize, it needs raw data. In this step, you will pull live news headlines from Google News using RSS feeds. This gives your bot a free, real-time stream of headlines without needing an API key or a paid subscription.
In this step, get ready to:
- Write a function that fetches news headlines from Google News RSS.
- Run your bot with multiple topics and see live headlines in your terminal.
Create the project file and write the news fetcher
RSS stands for Really Simple Syndication. It is a standardized format that websites use to publish updates as a feed you can read with code.
Why RSS instead of a news API?
Most news APIs charge money or cap you at 100 requests per month on their free tier. Google News RSS is completely free, requires no API key, and has no rate limits. It returns titles, sources, and publication dates, which is everything your bot needs.
- In your VS Code terminal, move to your Desktop by running this command:
cd %USERPROFILE%\Desktop
- Create your project folder and move into it by running these commands:
mkdir news-digest
cd news-digest
- Confirm you are inside the new folder by running:
cd
You should see a path ending in news-digest.
Folder already exists?
If you see an error that the folder already exists, skip the mkdir command and just run cd news-digest instead.
Help me with my project folder setup.
- Open this folder in VS Code by clicking File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop, select the news-digest folder, and click Select Folder.
You should see the news-digest folder name in the Explorer sidebar on the left.
- Create a new file by clicking File then New File.
- Name the file news_digest.py and press Enter to save it.
Your bot's entire logic will live in this one file. Time to write the function that pulls headlines from the web.
- In news_digest.py, paste the following code to create your news fetcher function:
import feedparser
from urllib.parse import quote
def fetch_news(topics):
"""Fetch news headlines from Google News RSS for multiple topics."""
# Store all articles and track titles we have already seen
all_articles = []
seen_titles = set()
for topic in topics:
# Build the Google News RSS search URL for this topic
url = f"https://news.google.com/rss/search?q={quote(topic)}&hl=en-US&gl=US&ceid=US:en"
feed = feedparser.parse(url)
for entry in feed.entries:
title = entry.title
# Only add articles we have not seen before
if title not in seen_titles:
seen_titles.add(title)
all_articles.append({
"title": title,
"link": entry.link,
"published": entry.get("published", "Unknown date"),
"source": entry.source.title if hasattr(entry, "source") else "Unknown",
})
return all_articles
What does this code do?
- feedparser is a library that reads RSS feeds and turns them into Python objects you can loop through.
- quote() encodes your search topic so it works safely in a URL (for example, spaces become %20).
- The function loops through each topic, fetches the Google News RSS feed for it, and collects every headline.
- seen_titles is a set that tracks headlines already collected. If the same headline appears under two different topics, the duplicate gets skipped.
- Save news_digest.py by pressing Ctrl+S.
Expand to multiple topics
The function is ready, but nothing calls it yet. You need a main block that passes in your topics and prints what it finds.
- At the bottom of news_digest.py, paste this code below the fetch_news function:
if __name__ == "__main__":
# Fetch headlines for three different topics
topics = ["technology", "business", "science"]
articles = fetch_news(topics)
# Print a count and the first 10 results
print(f"Fetched {len(articles)} unique articles across {len(topics)} topics.")
for a in articles[:10]:
print(f"- {a['title']} ({a['source']}) [{a['published']}]")
What does this code do?
- if __name__ == "__main__": makes this block run only when you execute the file directly (not when it is imported by another script).
- It passes three topics to fetch_news() and prints the total count of unique articles found.
- articles[:10] slices the list to show only the first 10 headlines, keeping the output readable.
- Save news_digest.py by pressing Ctrl+S.
Before you run this, what do you think will happen? Will you see headlines, or will something go wrong?
- Open a terminal in VS Code by pressing Ctrl+` (backtick key, usually above Tab).
- Run your bot by entering this command:
python news_digest.py
You should see a line like Fetched 28 unique articles across 3 topics. followed by 10 headlines with their sources and dates. Your exact count and headlines will differ because the news changes constantly.
Not seeing any headlines?
- If you see Fetched 0 unique articles, your network may be blocking Google News. Try running the script on a different network (for example, a mobile hotspot).
- If you see ModuleNotFoundError: No module named 'feedparser', make sure you installed the packages in the same terminal session. Run pip install feedparser==6.0.14 again.
- If you see a SyntaxError, double check that your code matches the chunks above exactly. Missing colons, quotes, or indentation are the most common culprits.
Help me debug my news fetcher.
✔️ Awesome, I've got everything!
Great. Make sure you have saved news_digest.py before moving on.
ⓧ I'd like to double check the full code
Scroll up and confirm your news_digest.py has these sections in order:
- The two imports at the top (feedparser and quote).
- The fetch_news() function with its loop, deduplication logic, and return all_articles at the end.
- The if __name__ == "__main__": block that defines three topics, calls fetch_news(), and prints the results.
Your bot is pulling live headlines from three different news topics. Next up, you will send these headlines to an AI model that picks the best stories and writes summaries for each one.
Add AI-Powered Curation
Your bot can already pull dozens of live headlines from Google News. The problem is that scrolling through 30+ raw headlines to find the ones that actually matter is exhausting.
This is exactly the kind of task AI handles well. In this step, you will connect your bot to the Groq API and use the Llama 3.3 70B model to read every headline, pick the top 5 stories, and write a short summary for each.
In this step, get ready to:
- Connect to the Groq API and send your headlines for AI analysis.
- Add error handling so the bot handles unexpected AI responses gracefully.
Connect to the Groq API and send headlines for analysis
To use the Groq API in Python, you need three new imports: os for reading your API key from the environment, json for parsing the AI's response, and the Groq client from the SDK you installed earlier.
- In news_digest.py, find the import lines at the very top of the file:
import feedparser
from urllib.parse import quote
- Replace them with this expanded import block:
import os
import json
import feedparser
from urllib.parse import quote
from groq import Groq
What did you just add?
- os lets your script read the GROQ_API_KEY environment variable you set in Step 1.
- json parses the AI's text response into a Python data structure you can work with.
- Groq is the client class from the Groq SDK that handles authentication and API calls.
Now you will build the function that sends your headlines to the AI and gets curated stories back.
- In news_digest.py, scroll down to the blank line after the fetch_news function's return all_articles line.
- Add the following new function below fetch_news:
def curate_with_ai(articles, client):
"""Use Groq AI to pick the top 5 most interesting stories."""
headlines_text = "\n".join(
f"- {a['title']} (Source: {a['source']})" for a in articles
)
prompt = f"""You are a news editor. Below are today's news headlines.
Pick the 5 most interesting and important stories. For each, write a 2-sentence summary explaining why it matters.
Headlines:
{headlines_text}
Respond with ONLY valid JSON in this exact format:
[
{{
"title": "Original headline text",
"source": "Source name",
"summary": "Your 2-sentence summary"
}}
]"""
chat_completion = client.chat.completions.create(
messages=[
{"role": "system", "content": "You are a helpful news editor. Always respond with valid JSON only."},
{"role": "user", "content": prompt},
],
model="llama-3.3-70b-versatile",
temperature=0.7,
max_tokens=1024,
)
response_text = chat_completion.choices[0].message.content.strip()
curated = json.loads(response_text)
return curated
What does this function do?
The function takes your list of articles and a Groq client, then builds a prompt that feeds every headline to the AI. The prompt asks the model to act as a news editor, pick the 5 most interesting stories, and return structured JSON with a title, source, and summary for each.
The double curly braces {{ and }} inside the prompt are Python f-string escapes. They produce literal { and } characters in the text sent to the AI so it knows the exact JSON format you want back.
The function is ready, but your script's main block still only fetches and prints raw headlines. You need to update it to call curate_with_ai and display the results.
- Scroll to the bottom of news_digest.py and find the existing if __name__ == "__main__": block:
if __name__ == "__main__":
topics = ["technology", "business", "science"]
articles = fetch_news(topics)
print(f"Fetched {len(articles)} unique articles across {len(topics)} topics.")
for a in articles[:10]:
print(f"- {a['title']} ({a['source']}) [{a['published']}]")
- Replace the entire if __name__ == "__main__": block with this updated version:
if __name__ == "__main__":
topics = ["technology", "business", "science"]
articles = fetch_news(topics)
print(f"Fetched {len(articles)} unique articles across {len(topics)} topics.")
# Initialize Groq client and curate headlines
client = Groq(api_key=os.environ.get("GROQ_API_KEY"))
curated = curate_with_ai(articles, client)
print(f"AI curated {len(curated)} top stories from {len(articles)} headlines.")
for story in curated:
print(f"\n- {story.get('title')} ({story.get('source')})")
print(f" {story.get('summary')}")
What changed in the main block?
- Groq(api_key=os.environ.get("GROQ_API_KEY")) creates a Groq client using the environment variable you set in Step 1. The client handles authentication for every API call.
- curate_with_ai(articles, client) passes your fetched headlines and the client to the new function, which returns 5 curated stories.
- The old loop that printed raw headlines is gone. The new loop prints each curated story with its AI-generated summary.
- Save news_digest.py.
- Run the script to see your first AI-curated digest by running this command:
python news_digest.py
You should see a line showing how many articles were fetched, followed by 5 curated stories. Each story has a title, source, and a 2-sentence AI-written summary.
Seeing an error?
- If you see AuthenticationError, your GROQ_API_KEY environment variable is not set in this terminal session. Run set GROQ_API_KEY=your-api-key-here again in the same terminal window where you run the script.
- If you see json.decoder.JSONDecodeError, the AI wrapped its response in markdown formatting. This is expected for now. You will fix this in the next substep.
- If you see ModuleNotFoundError: No module named 'groq', make sure you installed the packages with pip install groq==1.6.0 feedparser==6.0.14 in the same Python environment you are running the script from.
help me debug my Groq API call in news_digest.py
Add error handling for malformed AI responses
AI models sometimes wrap their JSON output in markdown code fences or add extra text around it. When that happens, json.loads() crashes because it sees backtick characters instead of valid JSON.
- In news_digest.py, find these two lines inside the curate_with_ai function:
curated = json.loads(response_text)
return curated
- Replace those two lines with this error-handling block and the return statement:
try:
curated = json.loads(response_text)
except json.JSONDecodeError:
# Fallback: find the JSON array inside extra text
start = response_text.find("[")
end = response_text.rfind("]") + 1
if start != -1 and end > start:
curated = json.loads(response_text[start:end])
else:
print("Warning: Could not parse AI response as JSON.")
curated = []
return curated
What does this error handling do?
The try block attempts a clean JSON parse first. If the AI wrapped the JSON in markdown or added surrounding text, json.JSONDecodeError fires and the fallback kicks in.
The fallback searches the response for the first [ and last ] characters, which mark the start and end of the JSON array. It then parses just that slice. If even that fails, it returns an empty list so the script does not crash.
- Save news_digest.py.
Before you run the script, think about what you expect to see. How many curated stories should appear, and what will each one include?
- Run the script again to verify everything works end to end:
python news_digest.py
You should see the fetched article count, followed by exactly 5 curated stories. Each story has a title, a source in parentheses, and a 2-sentence AI-written summary below it.
Still seeing a JSONDecodeError?
- Make sure you replaced both the curated = json.loads(response_text) line and the return curated line. The try/except block and the return statement together replace both original lines.
- Check that the indentation of the try statement matches the rest of the function body (4 spaces in from def).
help me fix JSON parsing errors in my curate_with_ai function
✔️ Awesome, I've got everything!
Great. Double check you have saved news_digest.py before moving on.
ⓧ I'd like to double check the full code
Here is the complete news_digest.py as it should look at the end of this step. Scroll through and compare each section against your file.
Your file has three main sections: the import block at the top, the fetch_news function (unchanged from Step 2), the new curate_with_ai function, and the updated if __name__ block at the bottom. Confirm each one is present and in order.
Your bot now reads 30+ headlines and uses AI to distill them into 5 curated stories with summaries. Next up, you will turn those stories into a polished HTML page you can open in a browser and share as a newsletter.
Generate a Styled HTML Digest
Your bot can now fetch live news and use AI to pick the five best stories. Right now, those curated stories only live as text in your terminal.
Terminal output is not something you can share, publish, or send to a friend. In this step, you will transform the bot's output into a styled HTML digest file that looks like a real newsletter.
In this step, get ready to:
- Build an HTML template function that wraps curated stories in styled markup.
- Create a main() function that orchestrates the full pipeline and saves the digest to a file.
- Run the bot and open a professional news digest in your browser.
Build the HTML template function
The generate_html function takes the five curated stories and wraps each one in a styled card. It then places all the cards inside a full HTML page with a header, date line, and footer.
- In news_digest.py, find the import lines at the top of the file. Find this line:
import feedparser
- Add import datetime on a new line directly above it, so the imports now look like this:
import datetime
import feedparser
- Scroll down past the curate_with_ai function. Leave a blank line after its return curated line, then start the generate_html function by pasting this code:
def generate_html(curated_stories, date_str):
"""Generate a styled HTML digest from curated stories."""
stories_html = ""
for i, story in enumerate(curated_stories, 1):
stories_html += f"""
<div style="background: #ffffff; border-radius: 8px; padding: 20px; margin-bottom: 16px; border-left: 4px solid #2563eb;">
<h2 style="margin: 0 0 8px 0; color: #1e293b; font-size: 18px;">{i}. {story.get('title', 'Untitled')}</h2>
<p style="margin: 0 0 8px 0; color: #64748b; font-size: 13px;">Source: {story.get('source', 'Unknown')}</p>
<p style="margin: 0; color: #334155; font-size: 15px; line-height: 1.6;">{story.get('summary', 'No summary available.')}</p>
</div>"""
What does this code do?
- enumerate(curated_stories, 1) loops through the stories with a counter starting at 1, so each card is numbered.
- Each story becomes a styled <div> card with a white background, rounded corners, and a blue left border.
- story.get('title', 'Untitled') safely pulls each field with a fallback value if the key is missing.
- Still inside the generate_html function, add the full page template directly below the story loop by pasting this code:
html = f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI News Digest - {date_str}</title>
</head>
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f1f5f9; margin: 0; padding: 20px;">
<div style="max-width: 640px; margin: 0 auto;">
<div style="text-align: center; padding: 32px 0;">
<h1 style="margin: 0; color: #1e293b; font-size: 28px;">AI News Digest</h1>
<p style="margin: 8px 0 0 0; color: #64748b; font-size: 15px;">{date_str} | Curated by AI</p>
</div>
{stories_html}
<div style="text-align: center; padding: 24px 0; color: #94a3b8; font-size: 12px;">
<p>Generated at {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')} by AI News Digest Bot</p>
</div>
</div>
</body>
</html>"""
return html
What does the page template do?
- The template wraps the story cards inside a centered container with a light gray background, giving it a newsletter look.
- The header displays "AI News Digest" and today's date. The footer stamps the exact time the digest was generated.
- All styling is inline CSS, so the HTML file looks correct in any browser without needing a separate stylesheet.
- Save news_digest.py.
- Confirm the function loads without errors by running this command in your terminal:
python -c "from news_digest import generate_html; print('Template function loaded!')"
You should see Template function loaded! printed with no errors.
Seeing a SyntaxError or IndentationError?
- Make sure the second code block (the page template) is indented one level inside the generate_html function. The line html = f""" and return html should both start with four spaces.
- Check that import datetime appears in the import block at the top of the file. The page template uses datetime.datetime.now() for the footer timestamp.
- Confirm there is no gap between the story loop block and the page template block. They should sit inside the same function with no extra blank lines separating them.
Still stuck? Ask for help.
Wire up the main function and run the bot
Your template function is ready, but nothing calls it yet. You need a main() function to orchestrate the full pipeline: fetch, curate, generate HTML, and save the file.
Before building main(), you will add a small logging helper so the bot prints timestamped progress messages as it runs.
- In news_digest.py, add the log_result function below generate_html by pasting this code:
def log_result(message, log_file="digest_log.txt"):
"""Append a timestamped log entry."""
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log_entry = f"[{timestamp}] {message}\n"
with open(log_file, "a", encoding="utf-8") as f:
f.write(log_entry)
print(log_entry.strip())
What does log_result do?
Every time you call log_result("some message"), it writes a timestamped line to digest_log.txt and prints the same line to your terminal. This gives you a record of every time the bot runs, which becomes essential once you schedule it to run automatically.
- Save news_digest.py.
- Verify the logging helper works by running this quick test:
python -c "from news_digest import log_result; log_result('Test log entry')"
You should see a timestamped line like [2026-08-16 10:30:00] Test log entry printed in your terminal. A new file called digest_log.txt also appears in your project folder with that same line.
- Now find the existing if __name__ == "__main__": block at the bottom of the file. Delete everything from that line to the end of the file.
- Replace it with the first half of the main() function by pasting this code:
def main():
# Import webbrowser to auto-open the digest
import webbrowser
topics = ["technology", "business", "science"]
date_str = datetime.datetime.now().strftime("%B %d, %Y")
file_date = datetime.datetime.now().strftime("%Y-%m-%d")
log_result("Starting news digest generation...")
# Fetch headlines from all topics
articles = fetch_news(topics)
log_result(f"Fetched {len(articles)} unique articles across {len(topics)} topics.")
if not articles:
log_result("No articles found. Exiting.")
return
What does this first half do?
- date_str formats today's date for the digest header (e.g. "August 16, 2026"). file_date formats it for the filename (e.g. "2026-08-16").
- Every major action is wrapped in a log_result call so you can trace exactly what happened each time the bot runs.
- The early return stops the bot gracefully if no articles were found, instead of crashing later.
- Directly below the code you just pasted (still inside the main function), add the second half that curates, generates, and saves the digest:
# Send headlines to AI for curation
client = Groq(api_key=os.environ.get("GROQ_API_KEY"))
curated = curate_with_ai(articles, client)
log_result(f"AI curated {len(curated)} top stories from {len(articles)} headlines.")
if not curated:
log_result("AI curation returned no results. Exiting.")
return
# Generate the HTML digest and save it
html = generate_html(curated, date_str)
filename = f"digest_{file_date}.html"
with open(filename, "w", encoding="utf-8") as f:
f.write(html)
log_result(f"Digest saved to {filename}")
webbrowser.open(filename)
if __name__ == "__main__":
main()
What does the second half do?
- It initializes the Groq client and sends the headlines to the AI for curation, just like your earlier code did.
- generate_html(curated, date_str) turns the curated stories into a complete HTML page.
- The digest is saved to a date-stamped file like digest_2026-08-16.html, so each day's run creates a new file instead of overwriting the last one.
- webbrowser.open(filename) automatically opens the finished digest in your default browser.
- Save news_digest.py.
Before you run this, what do you think will happen? You have a pipeline that fetches news, sends it to AI, builds an HTML page, and opens the browser. How many log messages do you expect to see?
- Run the full bot by entering this command:
python news_digest.py
You should see four timestamped log messages in your terminal: "Starting news digest generation...", the article count, the curation count, and the saved filename. Your browser automatically opens with a styled news digest.
What should the digest look like?
The digest has a centered "AI News Digest" heading with today's date, followed by five numbered story cards. Each card shows a headline, source name, and a two-sentence AI-written summary. A small footer at the bottom displays the exact time the digest was generated.
Your stories will differ from anyone else's because the headlines and AI summaries change every time. That is expected.
- Confirm the file was created by running:
ls digest_*.html
You should see a file named something like digest_2026-08-16.html listed in the output.
Browser did not open or no HTML file appeared?
- Check your terminal for error messages. If you see a NameError mentioning datetime, make sure import datetime appears in the import block at the top of the file.
- If you see AI curation returned no results. Exiting. in the log, the Groq API returned an unexpected format. Run the script again. The AI occasionally produces slightly malformed JSON on the first try.
- Make sure your GROQ_API_KEY environment variable is still set in this terminal session. If you opened a new terminal, you need to set it again.
Still stuck? Ask for help.
✔️ Awesome, I've got everything!
Great. Double check you have saved news_digest.py.
ⓧ I'd like to double check the full code
The full file is long, so scroll up through your work and confirm these sections appear in order:
- Imports: os, json, datetime, feedparser, quote, and Groq.
- fetch_news(topics) function (from Step 2).
- curate_with_ai(articles, client) function (from Step 3).
- generate_html(curated_stories, date_str) function with the story card loop and the full page template ending in return html.
- log_result(message, log_file) function that writes to digest_log.txt.
- main() function that calls fetch, curate, generate, save, and webbrowser.open().
- if __name__ == "__main__": main() as the last two lines.
Your bot now fetches live news, curates it with AI, and produces a polished HTML digest you could paste into a newsletter. Next up, you will schedule it to run automatically every day so a fresh digest is waiting for you each morning.
Schedule It to Run While You Sleep
In the last step, you built a complete pipeline that fetches news, curates it with AI, and generates a polished HTML digest. The only catch is you still have to run it yourself every time.
Running the bot manually defeats the purpose of automation. In this step, you will schedule it with Windows Task Scheduler so it runs automatically every day, even while you sleep.
In this step, get ready to:
- Prepare your script for unattended execution by removing the browser auto-open.
- Create a scheduled task in Windows Task Scheduler that runs the bot daily.
- Test the scheduled task and verify the digest was generated.
Prepare the script for unattended execution
Right now your script opens a browser tab every time it runs. That is fine when you run it manually, but a browser popup at 6 AM is not what you want from an automated bot.
- In VS Code, open news_digest.py and scroll down to the main() function.
- Find these two lines:
import webbrowser
topics = ["technology", "business", "science"]
- Delete the import webbrowser line so the function starts directly with topics:
topics = ["technology", "business", "science"]
- Now scroll to the very bottom of main() and find this line:
webbrowser.open(filename)
- Delete that webbrowser.open(filename) line entirely.
- While you are in main(), add a few comments to label each stage of the pipeline. Update your main() function so it matches the code below:
def main():
topics = ["technology", "business", "science"]
date_str = datetime.datetime.now().strftime("%B %d, %Y")
file_date = datetime.datetime.now().strftime("%Y-%m-%d")
log_result("Starting news digest generation...")
# Step 1: Fetch news
articles = fetch_news(topics)
log_result(f"Fetched {len(articles)} unique articles across {len(topics)} topics.")
if not articles:
log_result("No articles found. Exiting.")
return
What changed here?
You removed import webbrowser and webbrowser.open(filename) so the script no longer pops up a browser window. You also added a # Step 1: Fetch news comment to label the first stage of your pipeline.
The rest of main() also needs step labels. Continue updating main() by adding the remaining comments.
- Add the # Step 2: Curate with AI and # Step 3: Generate HTML comments so the rest of main() matches this:
# Step 2: Curate with AI
client = Groq(api_key=os.environ.get("GROQ_API_KEY"))
curated = curate_with_ai(articles, client)
log_result(f"AI curated {len(curated)} top stories from {len(articles)} headlines.")
if not curated:
log_result("AI curation returned no results. Exiting.")
return
# Step 3: Generate HTML
html = generate_html(curated, date_str)
filename = f"digest_{file_date}.html"
with open(filename, "w", encoding="utf-8") as f:
f.write(html)
log_result(f"Digest saved to {filename}")
What do these comments do?
They do not change how your code runs. They label each stage of your pipeline (fetch, curate, generate) so anyone reading the script can quickly understand the flow. This is especially helpful when debugging a scheduled task that runs without supervision.
- Save news_digest.py.
- Before you run the script, what do you expect to happen differently this time compared to last step?
- Run the bot to confirm it works without the browser popup:
python news_digest.py
You should see timestamped log messages in your terminal and a new digest_ HTML file in your project folder. This time, no browser window opens automatically.
Still seeing a browser pop up?
- Make sure you deleted both the import webbrowser line and the webbrowser.open(filename) line, then saved the file.
- Check that there are no other references to webbrowser anywhere in the file.
Still stuck? Why is my script still opening a browser after I removed webbrowser?
✔️ Awesome, I've got everything!
Great. Make sure you have saved news_digest.py with all of your changes.
ⓧ I'd like to double check the full code
Your news_digest.py is longer than 30 lines, so here is a checklist of the sections you should see in order. Scroll through your file and confirm each one is present:
- Imports at the top: os, json, datetime, feedparser, quote, Groq. No webbrowser import anywhere.
- The fetch_news() function (unchanged from the previous step).
- The curate_with_ai() function (unchanged).
- The generate_html() function (unchanged).
- The log_result() function (unchanged).
- The main() function: no import webbrowser, no webbrowser.open(filename), and three comments: # Step 1: Fetch news, # Step 2: Curate with AI, # Step 3: Generate HTML.
- The if __name__ == "__main__": block at the bottom calling main().
Create the scheduled task in Windows Task Scheduler
The set command you used earlier to set your API key only lasts for the current terminal session. Windows Task Scheduler runs in its own session, so it needs the key stored permanently.
- Make your API key permanent by running this command in your terminal:
setx GROQ_API_KEY "your-api-key-here"
What does setx do?
The setx command writes an environment variable to the Windows registry so it persists across all future sessions. Unlike set, which only works in the current terminal, setx makes the variable available to any new process, including Task Scheduler tasks.
The change takes effect in new terminal windows. Your current terminal still has the key from the earlier set command, so your script continues to work either way.
- Next, you need the full path to your Python installation. Find it by running:
where python
Copy the path that appears. It will look something like C:\Users\YourName\AppData\Local\Programs\Python\Python311\python.exe. You will need this in a moment.
- Open Task Scheduler by pressing the Windows key, typing taskschd.msc, and pressing Enter.
- In the right-hand Actions panel, click Create Basic Task....
- Enter AI News Digest as the name and click Next.
- Select Daily and click Next.
- Set the start time to a time of your choosing (for example, 6:00 AM) and click Next.
- Select Start a program and click Next.
Now you need to fill in three fields that tell Task Scheduler exactly what to run and where.
- In the Program/script field, paste the full path to your python.exe (the one you copied from where python earlier).
- In the Add arguments (optional) field, enter the full path to your news_digest.py file.
- In the Start in (optional) field, enter the path to your project folder (the folder containing news_digest.py). Do not include quotes in this field.
- Click Next, then click Finish.
Why does Start in matter?
The Start in field sets the working directory for the task. Your script saves the HTML digest and log file relative to where it runs. Without this field, Task Scheduler might run your script from a system folder, and the output files would end up somewhere unexpected.
Not sure what paths to use?
- For Program/script: paste exactly what where python printed. If it printed multiple paths, use the first one.
- For Add arguments: open news_digest.py in File Explorer, right-click it, and select Copy as path.
- For Start in: use the folder path from the step above, minus the file name. Do not include quotes.
Need more help? Help me figure out the correct paths for my Task Scheduler setup.
Test and verify the scheduled task
The task is configured to run daily, but you do not have to wait until tomorrow to find out if it works.
- Before you trigger the task, think about what you expect to see. Will a new HTML file appear? Will the log file update?
- In Task Scheduler, find your AI News Digest task in the list. Right-click it and select Run.
- Wait about 30 seconds for the task to complete.
- Confirm the digest was generated by checking your project folder:
dir digest_*.html
You should see at least one HTML file with today's date in the name.
- Check the log file to confirm the bot ran successfully:
type digest_log.txt
You should see timestamped entries ending with a line that says the digest was saved.
Task ran but no files appeared?
- Double-check that the Start in (optional) field in your task's action points to your project folder. Without it, files get saved to a different directory.
- Make sure you ran setx GROQ_API_KEY earlier. Task Scheduler runs in a new session that does not inherit your terminal's set variables.
- Check the task's Last Run Result column in Task Scheduler. A value of 0x0 means success. Anything else means the script hit an error.
Still stuck? Help me debug my Windows Task Scheduler task for news_digest.py.
Your AI news digest bot is fully automated. Tomorrow morning, check your project folder and you will find a fresh digest waiting for you. Next up, take on the Secret Mission to make your digest stand out.
Secret mission
Add a "Top Pick" Deep Dive
Your digest lists five curated stories, but every great newsletter leads with a star piece. Can you add a second AI pass that selects the single most important story and writes a deep-dive analysis, then render it as a highlighted section above the regular cards?
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project uses free services and local files, so there are no ongoing costs.
Resources you used:
- Project files: news_digest.py, generated digest_YYYY-MM-DD.html files, and digest_log.txt.
- Windows Task Scheduler task named AI News Digest.
- Python packages: groq and feedparser.
Keep everything running
No action needed. Choose this if you are still actively building or want the bot to keep generating daily digests.
- Your AI News Digest scheduled task continues to run daily at the time you set.
- New digest_YYYY-MM-DD.html files appear in your project folder each day.
- Your Groq account remains active on the free tier with no charges.
Pause - I'll come back to this later
Stop the bot from running automatically while keeping all your files intact so you can resume later.
- Open Windows Task Scheduler by pressing the Windows key, typing Task Scheduler, and pressing Enter.
- Find the AI News Digest task in the task list.
- Right-click the task and select Disable.
- When you want to resume, right-click the task again and select Enable.
Delete - I don't want to use this again
Remove all project resources and start fresh.
Delete the scheduled task
- Open Windows Task Scheduler by pressing the Windows key, typing Task Scheduler, and pressing Enter.
- Find the AI News Digest task in the task list.
- Right-click the task and select Delete.
- Confirm the deletion when prompted.
Uninstall the Python packages
- Open a terminal in VS Code and run the following command:
pip uninstall groq feedparser -y
Delete your project files
- Navigate to the folder containing news_digest.py on your computer.
- Delete news_digest.py, all digest_YYYY-MM-DD.html files, and digest_log.txt.
- If you created a dedicated project folder for this project, you can delete the entire folder.
Your Groq account is free
Your Groq account is on the free tier and costs nothing to keep. If you want to delete it, visit your account settings on console.groq.com.
That's a Wrap!
That's a Wrap!
Nice work! You just built a fully automated AI news digest bot that fetches live headlines, curates them with AI, and generates a professional HTML digest every single day while you sleep.
You've learned how to:
- Built a Python bot that fetches live news from Google News RSS feeds across multiple topics.
- Connected to the Groq API to use Llama 3.3 70B AI for intelligent curation and summarization.
- Generated professional, styled HTML digests ready for newsletter or blog publishing.
- Scheduled the bot to run automatically using Windows Task Scheduler so it creates fresh digests without lifting a finger.
- Secret Mission: Added a "Top Pick" deep-dive analysis feature that makes the digest stand out with a two-pass AI pipeline.
Ready to quiz yourself?