How to Build a Daily AI News Briefing with n8n, Ollama and Telegram on a Mac
A plain-language, step-by-step guide to a free, private morning briefing: n8n in Docker runs two news searches and a GitHub search, a local model in Ollama writes it up, and a Telegram bot delivers it, with every node's settings, a flow diagram and the traps that cost the most time.
TL;DR
- A free, private AI news briefing can land on your phone every morning: n8n runs the steps, SearXNG and GitHub find the news, a local model in Ollama writes it up, and a Telegram bot delivers it.
- Search twice, not once. A single news search can be swamped by one big story, so the workflow runs two and merges them.
- For new open-source projects, ask GitHub's search API for repositories created this week, sorted by stars. It answers "what is suddenly popular" far better than web search.
- Most of the time goes on small traps: the Code node has no
URL, a wire out of Merge and back in makes a loop, edits only run on schedule after Publish, and Telegram's Markdown breaks on underscores in repository names.
A morning briefing that writes itself, on your own computer
I wanted one short message each morning: which AI models came out, what else happened in the industry, and which new agent projects people were starring on GitHub. Ten lines I could read over coffee.
Now it arrives at 08:00 every day as a Telegram message. Nothing about it runs in the cloud except the searches themselves and the delivery. A Mac at home does the work: it searches, trims the results, asks a local language model to write the briefing, and sends it. There is no subscription and no per-message cost, because the model runs on the Mac.
The tool that strings the steps together is n8n, a workflow automation platform whose name is short for "nodemation". You draw boxes (called nodes), connect them with wires, and n8n runs them in order on a timer. Its code is published under what the company calls a fair-code licence, which allows self-hosting, so running it on your own Mac costs nothing. That suits this job well. The steps never change, nothing needs judgement, and when something breaks you can open the run and see exactly which box failed and what it received.
This guide is the exact workflow I use, node by node, written so you can follow it without a programming background or hand it to an AI coding assistant.
How the pieces fit together
Five parts, one job each:
- n8n is the conductor. It runs in Docker on your Mac, wakes at 08:00, and runs the steps in order.
- SearXNG is the news search. It is a free metasearch engine you run yourself: it asks other search engines and hands back their results as data n8n can read, with no account or API key, and its users are neither tracked nor profiled.
- GitHub's search API finds new repositories. It is free to call without a key for a job this small.
- Ollama is the writer. It runs a language model on your Mac and turns a list of headlines into a short briefing.
- Telegram is the delivery. A bot you create sends the message to your phone.
n8n, in Docker on your Mac
+------------------------------------------------------------------+
| |
| Schedule Trigger (08:00 every day) |
| | |
| +--> AI News search --------------+ |
| | (SearXNG, "AI new model | |
| | release news") | |
| | | |
| +--> AI industry search ----------+--> Merge (waits for all |
| | (SearXNG, "AI companies") | three searches) |
| | | | |
| +--> GitHub agent repos search ---+ v |
| (repos created this week, Shape for the model |
| by stars) (Code: trim to |
| numbered lines) |
| | |
| v |
| Write the briefing <---+-- Ollama on your Mac
| (local model) | (the model writes it)
| | |
| v |
| Send a text message ----+--> Telegram --> your phone
+------------------------------------------------------------------+Two things about this picture are worth knowing. The three searches all hang off the trigger and run side by side; none of them feeds another. And n8n only ever sends to Telegram, it never waits for a message from it, so your Mac opens no port to the internet and you need no tunnel or public address.
Set up the pieces: Docker, n8n, SearXNG and Ollama
What you need
- A Mac with Apple Silicon and 32 GB of memory for
gemma4:26b, the 18 GB model used here; with 16 GB, pick a smaller model. - Docker Desktop, installed and running.
- Telegram on your phone.
- The Terminal app, and about an hour.
Paste each command below into Terminal, press Enter, and compare the output with what the step expects.
Step 1: Start n8n in Docker
n8n keeps its workflows and saved passwords in a Docker volume, so they survive restarts and updates. Replace Europe/London with your own time zone name, such as America/New_York. The TZ setting controls what the container thinks the time is. GENERIC_TIMEZONE is the one n8n's schedules use, so set both or the briefing arrives at the wrong hour:
docker volume create n8n_data
docker run -d --name n8n --restart unless-stopped \
-p 127.0.0.1:5678:5678 \
-e GENERIC_TIMEZONE="Europe/London" -e TZ="Europe/London" \
-v n8n_data:/home/node/.n8n \
n8nio/n8n127.0.0.1:5678 means only your own Mac can open the editor; nobody else on your network can. The -v line mounts the n8n_data volume to the /home/node/.n8n directory to persist your data across container restarts, and --restart unless-stopped brings n8n back after Docker restarts. Wait half a minute, then check:
curl -s http://localhost:5678/healthzYou should see {"status":"ok"}. Open http://localhost:5678 in your browser and create the owner account. It lives only in that volume on your Mac.
Step 2: Start SearXNG with JSON turned on
SearXNG only answers in JSON, the format n8n reads, if its settings allow it. The formats it will serve are defined in settings.yml, under the search: section, and out of the box that list has web pages only. Write a small settings file first:
mkdir -p "$HOME/searxng"
cat > "$HOME/searxng/settings.yml" <<'EOF'
use_default_settings: true
server:
limiter: false
image_proxy: false
search:
formats:
- html
- json
EOF
docker run -d --name searxng --restart unless-stopped \
-p 127.0.0.1:8888:8080 \
-e SEARXNG_SECRET="$(openssl rand -hex 32)" \
-v "$HOME/searxng/settings.yml:/etc/searxng/settings.yml:ro" \
searxng/searxng:latestCheck it answers with news as data:
curl -s "http://localhost:8888/search?q=AI&categories=news&format=json" | head -c 80You should see text starting with {"query": "AI". Requesting an unset format will return a 403 Forbidden error, so if you get 403 Forbidden, the json line is missing from the settings file; fix it and run docker restart searxng.
Step 3: Make sure Ollama and a model are ready
If you do not have Ollama yet, install it from its website or with brew install ollama && brew services start ollama. Then pull a model that writes clear English. I use Gemma 4 at 26B; any capable instruction model works, because this job only asks the model to write, never to call tools.
ollama pull gemma4:26b
curl -s http://localhost:11434/api/tags | head -c 80The second line should print text starting with {"models":.
Step 4: The one networking idea you need
Inside a Docker container, localhost means the container itself, not your Mac. So n8n cannot reach Ollama at localhost:11434 or SearXNG at localhost:8888. Docker Desktop gives the Mac a name that works from inside: host.docker.internal. Every address in the workflow uses it. Check it from inside the n8n container:
docker exec n8n sh -c 'wget -qO- http://host.docker.internal:11434/api/tags' | head -c 80
docker exec n8n sh -c 'wget -qO- "http://host.docker.internal:8888/search?q=AI&categories=news&format=json"' | head -c 40They should print {"models": and {"query": "AI". If either fails here but works from Terminal, the address is the problem, not the service.
Connect Telegram and Ollama to n8n
Step 5: Make a second Telegram bot
If you already have a bot for something else (a chat assistant, say), make a new one for n8n. A bot token has only one place receiving its incoming messages, and your assistant already holds it; give n8n its own token and the two never compete, even if you later want n8n to react to messages too.
- In Telegram, open @BotFather and send
/newbot. - Give it a name, then a username ending in
bot. - Copy the token it replies with and keep it to yourself: whoever has it controls the bot.
- Open your new bot and send it
/start. A bot cannot message you until you have. - Message @userinfobot and note the number it replies with. In a private chat, your chat id with any bot is that same number.
Step 6: Add the two credentials in n8n
In the n8n editor, open Credentials, then Add credential:
| Credential | Field | Value |
|---|---|---|
| Telegram API | Access Token | the token BotFather sent you |
| Ollama | Base URL | http://host.docker.internal:11434 |
Name them so you can tell them apart later, for example "Telegram briefing bot" and "Ollama local". Ollama's own n8n guide gives the same address: http://host.docker.internal:11434 (opens in a new tab) if running through docker. Note it has no /v1 at the end. Other tools that talk to Ollama the way they would talk to OpenAI use /v1; n8n's Ollama credential speaks Ollama's own API and fails with it.
Build the workflow node by node
Create a new workflow and name it "Morning topic digest". Add each node with the + button, rename it to the name in bold (the Code node finds the searches by name, so the names matter), and set only the fields listed.
Step 7: The trigger
Schedule Trigger. Trigger Interval: Days. Days Between Triggers: 1. Trigger at Hour: 8am. Trigger at Minute: 0.
Step 8: The three searches
All three are HTTP Request nodes, Method GET, with Send Query Parameters on. Connect each one straight from the Schedule Trigger.
AI News search. URL http://host.docker.internal:8888/search, with these query parameters:
| Name | Value |
|---|---|
| q | AI new model release news |
| categories | news |
| time_range | week |
| format | json |
AI industry search. The same node with q set to AI companies. (Duplicate the first node and change one field.)
Why two? SearXNG's news results came from one engine, Bing News, ten results a search. The week I built this, one big model launch filled all ten slots of a single search, and the briefing had nothing else to say. A second, broader query brings back the rest of the week. I tried eight second queries; AI companies worked best, and looser ones like "AI startup funding" returned stories about entertainment and rocket companies.
GitHub agent repos search. URL https://api.github.com/search/repositories, with these query parameters:
| Name | Value |
|---|---|
| q | agent created:>{{ $now.minus({ days: 7 }).toFormat('yyyy-MM-dd') }} |
| sort | stars |
| order | desc |
| per_page | 10 |
The q value is an expression: switch that field from Fixed to Expression before pasting it. It works out the date seven days ago each morning, so the search asks for repositories with "agent" in them created since then, most-starred first. Web search is poor at "what got popular this week"; this question answers it directly. You can make unauthenticated requests if you are only fetching public data, so no GitHub key is needed. The primary rate limit for unauthenticated requests is 60 requests per hour, and the search endpoints are stricter still, but this workflow makes one call a day. If you test it over and over and GitHub starts refusing, wait an hour rather than adding a token.
Step 9: Merge, then shape the results
Merge. Mode: Append. Number of Inputs: 3. Wire the three searches into inputs 1, 2 and 3, in any order. Merge's only job here is to wait until all three searches have finished.
Wire each search into its own Merge input and nothing else. My first version had a wire running out of Merge and back into one of its inputs, which is a loop: the run hung, then failed.
Shape for the model. A Code node, Language JavaScript, Mode Run Once for All Items. Paste this:
// Read each search by node name; Merge only made us wait for all three.
// Both news searches go into one list, minus repeated headlines.
const seen = new Set();
const news = [
...$('AI News search').first().json.results,
...$('AI industry search').first().json.results,
]
.filter(r => {
const key = r.title.toLowerCase().slice(0, 40);
if (seen.has(key)) return false;
seen.add(key);
return true;
})
.slice(0, 16)
.map((r, i) => `${i + 1}. ${r.title} (${r.url.split('/')[2]})\n ${(r.content || '').slice(0, 200)}`);
const repos = $('GitHub agent repos search').first().json.items.slice(0, 10)
.map((r, i) => `${i + 1}. ${r.full_name}, ${r.stargazers_count} stars: ${(r.description || 'no description').slice(0, 150)}\n ${r.html_url}`);
return [{ json: { news: news.join('\n'), repos: repos.join('\n') } }];It does three things. It joins both news lists and drops headlines whose first 40 characters repeat, which catches the same article copied across sites. It keeps 16 news lines and 10 repositories. And it turns each into one numbered line with the site name and a short snippet. A local model handed raw search data tends to skim it and miss things; short numbered lines are enough to write from and easy to check against.
One trap: r.url.split('/')[2] pulls the site name out of a link with plain string work. The obvious way, new URL(r.url).hostname, fails with "URL is not defined", because n8n's Code sandbox leaves the URL helper out.
Step 10: Write the briefing with the local model
Write the briefing. A Basic LLM Chain node. Source for Prompt: Define below. Paste this prompt, switching the field to Expression so the two {{ }} parts are filled in:
You write a short morning briefing for one reader on Telegram. Use only the material below; do not add facts.
AI NEWS (last 7 days):
{{ $json.news }}
NEW AGENT REPOS ON GITHUB (created in the last 7 days, by stars):
{{ $json.repos }}
Write plain text, no Markdown, under 1500 characters, in three parts:
New models: the models that made headlines, one line each, merging duplicate stories.
Other AI news: up to five items, one line each, the most significant first; skip opinion pieces and stock tips.
Agent repos to look at: the top five repos, as "name (stars): what it does", then its link."Use only the material below" keeps the model from filling gaps with things it half remembers. "Under 1500 characters" keeps the message a glance, well inside Telegram's limit.
Then click the Model connector under the chain and add an Ollama Chat Model: credential "Ollama local", model gemma4:26b (or whichever you pulled).
Step 11: Send it
Send a text message. A Telegram node, Resource Message, Operation Send Message. Credential: your briefing bot. Chat ID: your number from @userinfobot. Text: {{ $json.text }} as an expression (text is the field the chain puts its answer in). Under Additional Fields, add Append n8n Attribution and turn it off, and leave Parse Mode unset.
Plain text matters. Telegram's Markdown mode treats _ and * as formatting, and repository names are full of underscores, so on some mornings the message fails to send at all. Plain text always sends.
Step 12: Test it, then publish
Click Execute workflow. Each node turns green as it runs, and the message should reach your phone within a minute or two (the model takes most of that). Click any node to see exactly what it received and produced.
Then click Publish. This is the step that catches everyone: n8n saves your edits as a draft as you go, but the schedule runs only the published version. After every later edit, publish again, or tomorrow's run uses the old one.
Ways to use it
The same nine nodes work for any beat. Change the two news queries and the GitHub query, and adjust the three parts of the prompt.
- A different topic. Two queries such as
battery technologyandelectric vehicle companiesfor news, andbattery created:>...for repositories. Keep one narrow query and one broad one, so one big story cannot fill the briefing. - A weekly roundup. Set the trigger to Weeks, Monday at 8am, and
time_rangetomonth. Keep the repository window at seven days. - A team channel. n8n's Telegram docs note you must add your bot to a channel so that it can send messages to that channel. Do that, use the channel's or group's chat id, and everyone gets the same briefing without anybody paying for a news service.
Keep it running, and what does not work well
A two-minute health check
docker inspect -f '{{.Name}} {{.State.Running}} {{.HostConfig.RestartPolicy.Name}}' n8n searxng
docker exec n8n sh -c 'wget -qO- http://host.docker.internal:11434/api/tags' | head -c 80Each line of the first should end true unless-stopped; the second should print text starting with {"models":, which means n8n can still reach Ollama. In the editor, Executions shows every run: mode trigger means the schedule ran it, manual means you clicked the button.
The limits, honestly
- A sleeping Mac skips the day. If the Mac is asleep at 08:00, that day's run does not happen, and n8n does not catch up when it wakes. Set the Mac not to sleep, or have it wake just before with
sudo pmset repeat wakeorpoweron MTWRFSU 07:55:00. - Docker has to be running. Docker Desktop does not start at login unless you turn on "Start Docker Desktop when you sign in" in its settings. Without it, after a restart there is no n8n and no briefing.
- Every container needs a restart policy. If SearXNG was started without
--restart unless-stopped, n8n comes back after a Docker restart but the news searches fail.docker update --restart unless-stopped searxngfixes an existing container. - A failed run is silent. When a run fails, nothing is sent, and the only sign is a missing message. Fix it with a second workflow: an Error Trigger node wired to a Telegram node with the same bot and chat id, Text set as an expression to
Briefing failed at {{ $json.execution.lastNodeExecuted }}. Publish it, then open the briefing workflow's Settings and pick it as the Error workflow. - Your secrets live in one volume. n8n stores the bot token in
n8n_data, encrypted, and the directory still contains other important data like encryption keys, instance logs, and source control feature assets. Lose the volume and the credentials go with it, so back it up. Downloading the workflow from its menu gives you a file to keep; in my exports it names the credentials but carries no token, so it is safe to share once you have looked. - Self-hosting is on you. n8n recommends self-hosting for expert users, because mistakes can mean lost data or downtime. Keeping the editor on
127.0.0.1and the workflow to one read-only job limits what can go wrong, but updates are yours to do. - The model can still be wrong. It writes from headlines and snippets, not the articles. Treat the briefing as a list of things to look at, not a summary to quote.
Hand this guide to an agent
If you use an AI coding assistant, you can give it this page and say:
Follow this guide to build the daily AI news briefing on this Mac with n8n, SearXNG, Ollama and Telegram.
Do Steps 1 to 4 yourself and show me the output of each check.
Stop at Step 5 and tell me exactly what to do in Telegram; I will paste the token and my chat id into n8n myself.
Never print the bot token or write it into any file.
Then tell me, node by node, what to set in the n8n editor for Steps 7 to 12, and check the run with me before I click Publish.Start tonight with the Terminal work: n8n, SearXNG and Ollama, and the two checks from inside the n8n container. Once both answer, the rest is an evening of clicking boxes, and tomorrow's news is waiting on your phone at eight.
Sources
- N8n (opens in a new tab), Wikimedia Foundation, Inc.
- SearXNG Documentation (2026.9.30+a9d990033) (opens in a new tab), docs.searxng.org
- Install with Docker | Deploy | n8n Docs (opens in a new tab), docs.n8n.io
- Search API - SearXNG Documentation (2026.9.30+a9d990033) (opens in a new tab), docs.searxng.org
- n8n - Ollama (opens in a new tab), Ollama
- Rate limits for the REST API - GitHub Docs (opens in a new tab), GitHub Docs
- Telegram | Nodes | n8n Docs (opens in a new tab), docs.n8n.io
ain8nollamatelegramsearxngautomation