I built an AI bot that reads 15 news sites and sends me an expert commodities briefing every morning (3 modules only)

Hi everyone!

A few weeks ago I knew nothing about automation. Now every weekday at 17:00, this lands in my Telegram:

A full analyst-style briefing covering energy, metals, agriculture, central banks and geopolitical risk — written by AI after it reads 15 news sites. No RSS modules, no XML parsing. Just 3 modules. Here’s exactly how I built it.

The scenario:

Schedule → HTTP (Anthropic API with built-in web search) → Telegram (×2, because of Telegram’s character limit). The trick that makes it this small: instead of pulling articles with RSS modules and aggregating them, the AI’s web search tool does the reading itself — you just give it a list of allowed domains. My first version used RSS feeds for every source and kept breaking on malformed XML; this approach turned out far more stable and cut my operations from ~1,500/month to ~240.

Module 1: Schedule
Weekdays at 17:00. Nothing special.

Module 2: HTTP — the brain

  • URL: https://api.anthropic.com/v1/messages — Method: POST
  • Headers: x-api-key (your Anthropic key), anthropic-version: 2023-06-01, content-type: application/json
  • Body type: Raw / JSON string
  • Timeout: 300 seconds — the AI reading 15 sites takes 1–2 minutes, the default 40s kills the request

In the JSON body I define: the model (Claude Sonnet), max_tokens ~2800, the web_search tool with an allowed_domains list of 15 sites and max 5 searches per run, plus a system prompt telling the AI to act as a veteran commodities trader and geopolitical analyst and structure the output into sections (Energy / Metals / Ags / Central Banks / Geopolitical Risk / Trader’s Take).

Lessons that cost me hours:

  • Anthropic blocks some big domains from crawling (reuters.com, bbc.com, apnews.com) — the API error tells you which ones, just remove them and use crawlable alternatives (oilprice.com, mining.com, agweb.com…)
  • If max_tokens is too low, the briefing gets cut off mid-sentence. 2800 is my sweet spot.

Modules 3 + 4: Telegram

Telegram messages max out at 4,096 characters and a good briefing is longer, so:

  • First Telegram module sends: substring(join(map(2.data.content; "text"); newline); 0; 4000)
  • Second Telegram module, with a filter (total length > 4000), sends characters 4000–8000

The join(map(…)) part matters: the API returns the answer as multiple text blocks, so last(…) alone gives you only the final fragment — learned that the hard way too.

Costs: one run per weekday, 5 web searches, Sonnet — a few dollars of API credits last for months.

The bot also posts its daily briefing to a public Telegram channel, so you can judge the output quality live: Telegram: View @commoditiesdeskai

Happy to answer any questions about the setup — this community helped me a lot when I was fighting the RSS errors, so ask away!

Why are you using an HTTP module and not the dedicated Claude one?

And why is the key hardcoded in the module it self, instead of using the dedicated authentication method?

1 Like

Good questions, thanks!

  1. The main reason is the web_search tool. The whole briefing depends on Claude’s built-in web search with an allowed_domains list (my 15 sites) and a cap of 5 searches per run — when I built this, I couldn’t map those parameters in the dedicated Claude module’s fields, so I used the generic HTTP module where I control the raw JSON body 1:1 with the Anthropic API docs. As a beginner that also made debugging much easier, because every error message matched the docs exactly.

  2. Fair point on the key — that’s my beginner side showing :slightly_smiling_face: You’re right that storing it in a connection is the cleaner and safer way. I’ll test the Claude app’s “Make an API Call” module (stored connection + raw body) and if the web_search tool works the same through it, I’ll update the post.

Thanks for the feedback!

1 Like

Small update for anyone following this: I cloned the same architecture into a completely different niche — a daily sanctions & financial crime briefing (OFAC/EU/UK designations, enforcement fines, FATF updates) for compliance folks.

Total changes needed: exactly 3 — the system prompt, the user message, and the allowed_domains list. Everything else (HTTP module, the two-message Telegram split, scheduling) carried over untouched. ~40 minutes including testing.

Two new lessons that cost me debugging time:

  1. Never put double quote characters inside prompt text in the JSON body — use single quotes. One " inside the prompt breaks the whole body (“not valid JSON” error at some random position).
  2. Don’t ask the model to “keep under X characters” — models can’t count characters while writing. Structural rules work instead: “max 3 items per section, one short paragraph each” fixed my truncation problem completely.

Live output if you want to see the clone running: Telegram: View @sanctionsdeskai

Same 5-module pattern really is a template factory — swap 3 things, new product.

Hello,

You can use Make an API Call module- it uses your connection/connections so if you have to update API key- things getting much easier :slight_smile:

To make your scenario scalable it shouldnt be hardcoded- right now if your message exceeds 8000 chars- last part wont be sent.

Characters That Must Be Escaped

The JSON specification requires these characters to be escaped:

Character Escaped Description
" \" Double quote – must be escaped inside strings
\ \\ Backslash – the escape character itself as JSON expects something will be escaped after \
Newline \n New line char. (LF)
Tab \t Horizontal tab
Carriage return \r CR
Backspace \b

Thanks @mszymkowiak — great tip! :raising_hands:

Quick context on why I went with the raw HTTP module: I needed the web search tool with an allowed_domains whitelist in the request body, and I couldn’t find those fields in the dedicated Claude modules. But you’re absolutely right that hardcoding the API key in a header is the ugly part — one safe place to rotate keys is much cleaner.

I’ll test moving the call to Make an API Call so auth comes from the connection while keeping the custom JSON body (tools + allowed_domains). If it handles the anthropic-version header automatically too, even better. Will report back here once I’ve switched one of my scenarios over.

Exactly the kind of improvement I was hoping to collect by posting here — thanks again!

1 Like