Build an AI Cron: Turn RSS Feeds Into a Personalized Morning Newsletter
What We’re Building
A cron-style job that wakes up every morning, pulls articles from your favorite RSS feeds, runs them through a local LLM for filtering and summarization, and emails you a clean, personalized digest. No cloud costs, no API keys to manage—just open-source tools and free-tier services working together.
Feature list:
- Fetches multiple RSS feeds and deduplicates entries
- Filters out noise using a local LLM (Ollama) based on your interests
- Summarizes each selected article with a free Hugging Face model
- Formats everything into a clean HTML email
- Sends the digest on a schedule using n8n’s built-in cron
- Runs entirely on free tools
Architecture Overview
n8n acts as the orchestrator: it triggers on a schedule, fetches feeds, manages data flow, and sends the email. Ollama runs locally for filtering—no latency or rate limits. Hugging Face Inference API (free tier) handles summarization so we don’t burn local GPU on a larger model. The result is a pipeline that costs $0/month and respects your privacy.
Prerequisites (All Free Tier)
| Tool | Purpose | Free Tier Details |
|---|---|---|
| n8n | Workflow orchestration | Self-hosted (free) or n8n.cloud free tier (5 workflows) |
| Ollama | Local LLM for filtering | Open-source, runs on your machine |
| Hugging Face | Summarization API | Free Inference API (rate-limited, ~30k chars/month) |
| Mailtrap or Gmail SMTP | Email sending | Mailtrap free tier (100 emails/day) for testing; Gmail SMTP for production |
Hardware note: Ollama runs best on a machine with at least 8GB RAM. For this guide, we’ll use llama3.2:1b—a tiny model that runs on almost anything.
Step 1: Set Up Ollama Locally
Install Ollama from ollama.com and pull the lightweight model:
# Install Ollama (macOS/Linux)
curl -fsSL https://ollama.com/install.sh | sh
# Pull the 1B parameter model
ollama pull llama3.2:1b
# Verify it works
ollama run llama3.2:1b "Say hello in JSON: {\"greeting\": \"...\"}"
We’ll expose Ollama’s API locally on port 11434. n8n will call it via HTTP. Test the endpoint:
curl http://localhost:11434/api/generate -d '{
"model": "llama3.2:1b",
"prompt": "Is this article about AI or tech? Reply only YES or NO. Title: Apple releases new MacBook",
"stream": false
}'
Expected response: {"response": "YES"}. This is the core filtering mechanism.
Step 2: Create the Article Summarizer with Hugging Face
Sign up at huggingface.co and grab your free API token from Settings → Access Tokens. We’ll use facebook/bart-large-cnn, a solid summarization model that works well on the free tier.
Test the endpoint:
curl https://api-inference.huggingface.co/models/facebook/bart-large-cnn \
-H "Authorization: Bearer YOUR_HF_TOKEN" \
-H "Content-Type: application/json" \
-d '{"inputs": "The quick brown fox jumps over the lazy dog. This is a longer piece of text that needs summarization to extract the key points."}'
Note: The free tier may return a 503 on first call if the model is cold-starting. Wait 20-30 seconds and retry.
Step 3: Build the Workflow in n8n
We’ll build this in the n8n editor. If you’re self-hosting, run:
npx n8n
Or use n8n.cloud free tier. Create a new workflow and add these nodes:
3.1 Schedule Trigger
Add a Schedule Trigger node. Set it to run daily at 7:00 AM:
{
"rule": {
"interval": [{"field": "cron", "expression": "0 7 * * *"}]
}
}
3.2 Fetch RSS Feeds
Add an HTTP Request node for each feed. Example for Hacker News:
- Method: GET
- URL:
https://hnrss.org/frontpage?count=10 - Response Format: JSON
Add more feeds (e.g., TechCrunch, Ars Technica). Use n8n’s Merge node to combine all items into a single array.
3.3 Deduplicate and Filter with Code Node
Add a Code node to deduplicate by link and prepare items:
const items = $input.all();
const seen = new Set();
const unique = [];
for (const item of items) {
const link = item.json.link || item.json.url;
if (!seen.has(link)) {
seen.add(link);
unique.push({
json: {
title: item.json.title,
link: link,
summary: item.json.summary || item.json.content || '',
feed: item.json.feedTitle || 'Unknown'
}
});
}
}
return unique;
3.4 Ollama Filter Node
Add another Code node that calls Ollama for each article:
const articles = $input.all();
const filtered = [];
for (const article of articles) {
const prompt = `You are a content filter. Reply only YES or NO.
User interests: AI, machine learning, software engineering, open source, tech business.
Article title: ${article.json.title}
Article snippet: ${article.json.summary?.substring(0, 300)}
Does this article match the user's interests?`;
const response = await fetch('http://localhost:11434/api/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: 'llama3.2:1b',
prompt: prompt,
stream: false
})
});
const data = await response.json();
if (data.response.trim().toUpperCase() === 'YES') {
filtered.push(article);
}
}
return filtered;
Note: If n8n is running in Docker, use http://host.docker.internal:11434 instead of localhost.
3.5 Hugging Face Summarization Node
Add another Code node to summarize each filtered article:
const articles = $input.all();
const HF_TOKEN = 'YOUR_HF_TOKEN'; // Use n8n credentials in production
for (const article of articles) {
const input = article.json.summary || article.json.title;
const response = await fetch(
'https://api-inference.huggingface.co/models/facebook/bart-large-cnn',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${HF_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ inputs: input, parameters: { max_length: 100 } })
}
);
const data = await response.json();
article.json.ai_summary = data[0]?.summary_text || 'Summary unavailable';
}
return articles;
3.6 Compile HTML Email
Add a Code node to build the email body:
const articles = $input.all();
let html = `<h1>Your Morning Digest</h1><p>${new Date().toLocaleDateString()}</p>`;
for (const a of articles) {
html += `
<div style="margin-bottom:20px;">
<h2><a href="${a.json.link}">${a.json.title}</a></h2>
<p><em>${a.json.feed}</em></p>
<p>${a.json.ai_summary}</p>
</div>
`;
}
return [{ json: { html } }];
3.7 Send Email
Add an SMTP Send node. For Mailtrap testing:
- Host:
sandbox.smtp.mailtrap.io - Port: 2525
- User/Password: from Mailtrap dashboard
- From:
digest@example.com - To: your email
- Subject:
Your Morning Digest - {{$today.format('YYYY-MM-DD')}} - Email Type: HTML
- HTML Body:
{{ $json.html }}
For production, swap to Gmail SMTP (you’ll need an app password).
Step 4: Run and Schedule the Workflow
Click Execute Workflow in n8n to test. Check each node’s output. If everything looks good, activate the workflow. The schedule trigger will run it daily at 7 AM.
Pro tip: Add an Error Trigger node that emails you if anything fails. Connect it to the SMTP node with a different subject line.
Sensible Extensions
Once the core pipeline works, extend it:
- Add a webhook trigger so you can manually request a digest anytime (see our guide on n8n webhooks)
- Swap Ollama for a fine-tuned classifier if you have specific filtering needs (how to fine-tune with Hugging Face)
- Store past digests in a free SQLite database or Google Sheets for searchability
- Add sentiment analysis using another free Hugging Face model per article
- Integrate with Notion or Obsidian to archive summaries automatically (n8n Notion integration guide)
- Use a larger Ollama model (like
llama3.2:3bormistral:7b) if your hardware allows, for better filtering quality
Common Pitfalls
- Ollama not reachable from n8n Docker: Use
host.docker.internalinstead oflocalhoston Mac/Windows, or172.17.0.1on Linux. - Hugging Face cold starts: The free tier unloads models after inactivity. First call of the day may fail with 503. Add a retry loop in the Code node (3 retries, 10-second delay).
- Rate limits: HF free tier allows ~30k characters per month. If you process many feeds, batch articles or use a local summarization model via Ollama as fallback.
- Ollama memory usage: Even
llama3.2:1buses ~1GB RAM. Close other apps if you’re on a low-spec machine. - Email deliverability: Gmail SMTP has sending limits (500/day for personal accounts). For more volume, use SendGrid free tier (100 emails/day).
- RSS feed variability: Some feeds use
content, otherssummaryordescription. Normalize fields in the deduplication Code node.
FAQ
Q: Can I run this entirely in the cloud for free?
A: Yes—use n8n.cloud free tier, swap Ollama for Hugging Face’s free text-generation models (e.g., microsoft/phi-2), and use Gmail SMTP. Latency will be higher, and you’ll hit HF rate limits faster.
Q: How many feeds can I process? A: With Ollama locally, the bottleneck is Hugging Face summarization. At 30k characters/month, you can summarize ~100-150 articles depending on length. For more, self-host a summarization model with Ollama.
Q: What if I want a different schedule?
A: Change the cron expression in the Schedule Trigger. 0 8 * * 1-5 runs weekdays at 8 AM. Use crontab.guru to build expressions.
Q: How do I add more personalization? A: Expand the Ollama filter prompt with more specific interests, or add a separate LLM call that scores articles 1-10 and only includes those above a threshold.
Q: Is the Hugging Face free tier really enough?
A: For a personal morning digest, yes. If you need more, Hugging Face offers $9/month Pro tier with higher limits, or you can run summarization locally via Ollama with a model like mistral:7b.
Q: Can I send the digest via Telegram or Slack instead of email? A: Absolutely. Replace the SMTP node with n8n’s Telegram or Slack nodes. We have a guide on building Slack bots with n8n.
Want to build like a Forward Deployed Engineer?
FDE Coach is a cohort-based program in frontend, backend, AWS, and AI. Build real products and get referred to 200+ hiring partners.
Explore the program