Back to blog

November 26, 2025

AI News Agent: A Smarter Newsletter That Knows When to Send

An intelligent n8n workflow that decides when news is worth sending - with built-in deduplication, frequency guardrails, and AI-driven editorial decisions.

n8nautomationaiai agentsworkflowsnewsletter
Download the workflow
AI News Agent: A Smarter Newsletter That Knows When to Send

Overview

In my previous blog post, I shared a workflow that sends a weekly AI-curated newsletter. Although I was using it, it had limitations: it sent on a fixed schedule regardless of news quality, and couldn't remember what it had already covered.

This new AI News Agent workflow addresses those shortcomings. I use it myself for my AI and n8n news - and it's significantly smarter than its predecessor.

Key improvements over the previous version:

  • AI-driven send decision: Instead of blindly sending on a schedule, an AI agent evaluates whether today's news is worth sending.
  • Deduplication against past newsletters: The workflow compares candidate articles against the last 5 newsletters to avoid repetition.
  • Switched to DuckDuckGo News API: Replaces the previous NewsAPI setup with SerpAPI's DuckDuckGo News engine, providing more frequent and more recent news results.
  • Frequency guardrails: Configure minimum and maximum days between newsletters - the AI respects these boundaries while still making intelligent decisions.

In this post, I'll walk you through the complete workflow, explain each component, and show you how to set it up yourself.


You can download the workflow here and import it directly into your own n8n instance.

What this workflow does

At a high level, the AI News Agent:

  1. Fetches fresh news twice daily via SerpAPI's DuckDuckGo News engine.
  2. Stores articles in a persistent data table with automatic deduplication.
  3. Filters for freshness - only considers articles newer than your last newsletter.
  4. Applies frequency guardrails - respects your min/max sending preferences.
  5. Makes an editorial decision - AI evaluates if the news is worth sending.
  6. Enriches selected articles - uses Tavily web search for fact-checking and depth.
  7. Delivers via Telegram - sends a clean, formatted newsletter.
  8. Remembers what it sent - stores each edition to prevent future repetition.

This allows you to get newsletters only when there's genuinely relevant news - in contrast to a fixed schedule.

Requirements

To run this workflow, you need:

  • n8n instance
    Self-hosted or n8n cloud. If you haven't used n8n before:

    • n8n Cloud: create an account here
    • Self-hosted: follow the official docs here
  • SerpAPI key
    Create an account at serpapi.com and generate an API key. They offer 250 free searches/month.

  • Tavily API key
    Sign up at app.tavily.com and create an API key. Generous free tier available.

  • OpenAI API key
    Get one from OpenAI - required for AI agent calls.

  • Telegram bot + chat ID
    A free Telegram bot (via BotFather) and the chat/channel ID where you want the newsletter. See Telegram's bot tutorial for setup.

How it works

The workflow is organized into five logical stages.

Stage 1: Schedule & Configuration

  • Schedule Trigger
    Runs the workflow on a cron schedule. Default: 0 0 9,17 * * * (twice daily at 9:00 and 17:00). These frequent checks enable the AI to send newsletters at these times when it observes actually relevant news, not only once a week.

I picked 09:00 and 17:00 as natural check‑in points at the start and end of a typical workday, so you see updates when you’re most likely to read them without being interrupted in the middle of deep work. With SerpAPI’s 250 free searches/month, running twice per day with a small set of topics (e.g. 2–3) keeps you comfortably below the limit; if you add more topics or increase the schedule frequency, either tighten the cron window or move to a paid SerpAPI plan to avoid hitting the cap.

  • Set topics and language
    A Set node that defines your configuration:
    • topics: comma-separated list (e.g., AI, n8n)
    • language: output language (e.g., English)
    • minDaysBetween: minimum days to wait (0 = no minimum)
    • maxDaysBetween: maximum days without sending (triggers a "must-send" fallback)

Stage 2: Fetch & Store News

  • Build topic queries
    Splits your comma-separated topics into individual search queries:

In DuckDuckGo News via SerpAPI, a query like AI,n8n looks for news where both “AI” and “n8n” appear. For a niche tool like n8n, this is often almost identical to just searching for n8n (docs). It’s therefore better to split the topics, search for each of them separately, and let the AI later decide which news articles to select.

return $input.first().json.topics.split(',').map(topic => ({
  json: { topic: topic.trim() }
}));
  • Fetch news from SerpAPI (DuckDuckGo News)
    HTTP Request node calling SerpAPI with:

    • engine: duckduckgo_news
    • q: your topic
    • df: d (last day)

    Auth is handled via httpQueryAuth credentials with your SerpAPI key.

SerpAPI also offers other news engines such as the Google News API (see here). DuckDuckGo News is used here because, unlike Google News, it returns an excerpt/snippet in addition to the title, source, and URL (see here)—giving the AI more context to work with.

Another option is NewsAPI as used in my previous post, but its free tier delays articles by 24 hours, so you miss the freshness window that makes these twice-daily checks valuable. DuckDuckGo News through SerpAPI keeps the workflow real-time without that lag.

n8n has official SerpAPI nodes, but as of writing there is no dedicated node for the DuckDuckGo News API. That’s why this workflow uses a custom HTTP Request node instead, which works the same under the hood while giving you full control over the DuckDuckGo News parameters.

  • Split SerpAPI results into articles
    Expands the results array so each article becomes its own item.

  • Upsert articles into News table
    Stores each article in an n8n data table with fields: title, source, url, excerpt, date. Uses upsert on title + URL to avoid duplicates. Date is normalized to ISO UTC:

DateTime.fromSeconds(Number($json.date), {zone: 'utc'}).toISO()

Stage 3: Filtering & Frequency Guardrails

This is where the workflow gets smart about what to consider and when to send.

  • Get previous newsletters → Sort → Get most recent
    Pulls all editions from the Newsletters table and isolates the latest one with its createdAt timestamp.

  • Combine articles with last newsletter metadata
    Attaches the last newsletter timestamp to each candidate article.

  • Filter articles newer than last newsletter
    Keeps only articles published after the last edition. Uses a safe default date (2024-01-01) if no previous newsletter exists:

$json.date_2 > ($json.createdAt_1 || DateTime.fromISO('2024-01-01T00:00:00.000Z'))
  • Stop if last newsletter is too recent
    Compares createdAt against your minDaysBetween setting. If you're still in the "too soon to send" window, the workflow short-circuits here.

Stage 4: AI Editorial Decision

This is the core intelligence of the workflow - an AI that decides whether to send and what to include. This stage is also the actual agentic part of the workflow, where the system makes its own decisions instead of just following a fixed schedule.

  • Aggregate candidate articles for AI
    Bundles today's filtered articles into a compact list with title, excerpt, source, and url.

  • Limit previous newsletters to last 5 → Aggregate
    Prepares the last 5 newsletter contents for the AI to check against for repetition.

  • Combine candidate articles with past newsletters
    Merges both lists so the AI sees "today's candidates" + "recent history" side by side.

  • AI: decide send + select articles
    The heart of the workflow. A GPT-5.1 call with a comprehensive editorial prompt:

Prompt: AI decide send + select articlesShow / hide prompt
You are an **AI Newsletter Editor**. Your job is to decide whether today’s newsletter edition should be sent, and to select the best articles.

You will receive a list of articles with:
'title', 'excerpt', `source`, `url`.

You will also receive content of **previously sent newsletters** (markdown).

# Your Tasks

## 1. Decide whether to send the newsletter

Output "YES" only if all of the following are satisfied **OR** the fallback rule applies:

### **Base Criteria**

1. There are **at least 3 meaningful articles**.
   *Meaningful = not trivial, not purely promotional, not clickbait, contains actual informational value.*

2. Articles must be **non-duplicate and non-overlapping**:

   * Not the same topic/headline rephrased
   * Not reporting identical events with minor variations
   * Not the same news covered by multiple sources without distinct insights

3. Articles must be **relevant to the user's topics**:
   **{{ $('Set topics and language').item.json.topics }}**

4. Articles must be **novel** relative to the **topics in previous newsletters**:

   * Compare against all previous newsletters below
   * Exclude articles that discuss topics already substantially covered

5. Articles must offer **clear value**:

   * New information
   * Impact that matters to the user
   * Insight, analysis, or meaningful expansion

### **Fallback rule: Newsletter frequency requirement**

If **at least 1 relevant article exists** *and*
the last newsletter was sent **more than {{ $('Set topics and language').item.json.maxDaysBetween }} days ago**, then you **MUST** return "YES" as a decision even if the other criteria are not completely met.

Last newsletter was sent {{ $('Get most recent newsletter').item.json.createdAt ? Math.floor($now.diff(DateTime.fromISO($('Get most recent newsletter').item.json.createdAt), 'days').days) : 999 }} days ago.

### Otherwise → "NO"

## **2. If "YES": Select Articles**

Select the **top 3–5** articles that best fulfill the criteria above.

For each selected article, output:

* **title** (rewrite for clarity, conciseness, and impact)
* **summary** (1–2 sentences; written in the output language)
* **source**
* **url**

All summaries **must** be written in:
**{{ $('Set topics and language').item.json.language }}**

---

# **Output Format (JSON)**

{
  "decision": "YES or NO",
  "articles": [
    {
      "title": "...",
      "summary": "...",
      "source": "...",
      "url": "..."
    }
  ]
}

When "decision": "NO", return an empty array for "articles".

# **Article Input**

Use these articles:

{{
  $json.results.map(
   article =>
    `Title: ${article.title_2}
     Excerpt: ${article.excerpt_2}
     Source: ${article.source_2}
     URL: ${article.url_2}`
  ).join('\n---\n')
}}

You must also consider the topics already covered in previous newsletters to avoid repetition:

{{ $json.newsletters.map(x => `Newsletter: ${x.content}`).join('\n---\n') }}

The AI outputs structured JSON:

{
  "decision": "YES",
  "articles": [
    {
      "title": "...",
      "summary": "...",
      "source": "...",
      "url": "..."
    }
  ]
}
  • If AI decided to send newsletter
    Routes based on decision === "YES". If NO, the workflow ends gracefully.

Stage 5: Content Enrichment & Delivery

  • Split selected articles for enrichment
    Each selected article becomes its own item for individual processing.

  • AI: enrich & write article
    An AI Agent node with GPT-5.1 + Tavily web search tool. For each article:

Prompt: AI enrich & write articleShow / hide prompt
You are a research writer that updates short news summaries into concise, factual articles.

**Input:**
Title: {{ $json["title"] }}
Summary: {{ $json["summary"] }}
Source: {{ $json["source"] }}
Original URL: {{ $json["url"] }}
Language: {{ $('Set topics and language').item.json.language }}

**Instructions:**

1. Use **Tavily Search** to gather 2–3 reliable, recent, and relevant sources on this topic.
2. Update the **title** if a more accurate or engaging one exists.
3. Write **1–2 sentences** summarizing the topic, combining the original summary and information from the new sources.
4. Return the original source name and url as well.

**Output (JSON):**

{
  "title": "final article title",
  "content": "concise 1–2 sentence article content",
  "source": "the name of the original source",
  "url": "the url of the original source"
}

**Rules:**

* Ensure the topic is relevant, informative, and timely.
* Translate the article if necessary to comply with the desired language {{ $('Set topics and language').item.json.language }}.

The Output Parser enforces the JSON schema with title, content, source, and url fields.

  • Aggregate enriched articles
    Collects all enriched articles back into a single array.

  • Insert newsletter content into Newsletters table
    Stores the final markdown content for future deduplication:

$json.output.map(article => {
  const title = JSON.stringify(article.title).slice(1, -1);
  const content = JSON.stringify(article.content).slice(1, -1);
  const source = JSON.stringify(article.source).slice(1, -1);
  const url = JSON.stringify(article.url).slice(1, -1);
  return `*${title}*\n${content}\nSource: [${source}](${url})`;
}).join('\n\n')
  • Send newsletter to Telegram
    Sends the formatted newsletter to your Telegram chat/channel.

Why this workflow is powerful

  • Intelligent send decisions
    The AI evaluates news quality before sending, leading to a less noisy and more relevant news digest.

  • Memory across editions
    By persisting newsletters and comparing against history, the workflow avoids repetition.

  • Frequency guardrails with flexibility
    Set boundaries (e.g., "at least 1 day between sends" and "must send within 5 days"), but let the AI decide the optimal moment within those bounds.

  • Source-level deduplication
    The news table with upsert prevents the same article from being considered multiple times across runs.

  • Grounded in facts
    SerpAPI provides real news sources; Tavily enriches with additional verification. The newsletter stays factual.

  • Configurable and extensible
    Change topics, language, frequency - all in one Set node. In addition, the workflow is modular, allowing to add new news sources or new delivery channels without touching the core logic.

Configuration guide

To customize this workflow for your needs:

  1. Topics and language
    Open Set topics and language and modify:

    • topics: your interests (e.g., machine learning, startups, TypeScript)
    • language: your preferred output language
  2. Frequency settings

    • minDaysBetween: minimum days between newsletters (0 = no limit)
    • maxDaysBetween: maximum gap before forcing a send
    • For very high-volume topics (such as "AI"), expect the workflow to send almost every time once minDaysBetween has passed, because the content-quality criteria are usually met.
  3. Schedule
    Modify the Schedule Trigger cron expression. Default runs twice daily at 9:00 am and 5:00 pm; adjust to your preference.

  4. Telegram
    Update the chatId in the Telegram node to your chat/channel.

  5. Credentials
    Set up credentials for: SerpAPI (httpQueryAuth), Tavily, OpenAI, Telegram.

Next steps and improvements

Here are concrete directions to take this workflow further:

  • Multi-agent architecture
    Split the current AI calls into specialized agents: signal detection, relevance scoring, editorial decision, content enhancement, and formatting - each with a single responsibility.

  • 1:1 personalization
    Move from static topics to weighted preferences. Learn from click behavior and feedback.

  • Telegram feedback buttons
    Add inline buttons (👍 Useful / 👎 Not relevant / 🔎 More like this) and feed signals back into ranking.

  • Email with HTML template
    For more flexibility, send the newsletter via email.

  • Incorporating other news APIs or RSS feeds
    Add more sources such as other news APIs and RSS feeds from blogs, newsletters, or communities.

  • Adjust for arxiv paper search and research news
    Swap SerpAPI for arxiv search or other academic sources to obtain a personal research digest newsletter.

  • Images and thumbnails
    Fetch representative images for each article and include them in the newsletter.

  • Web archive
    Auto-publish each edition as a web page with permalinks.

  • Retry logic and error handling
    Add exponential backoff for external APIs and route failures to an error workflow.

  • Prompt versioning
    Move prompts to a data table with versioning for A/B testing and rollback.

  • Audio and video news
    Use audio or video models for better news communication.

Wrap-up

This AI News Agent workflow represents a significant evolution from simple scheduled newsletters. By adding intelligent send decisions, historical deduplication, and frequency guardrails, you get a newsletter that respects the quality of available news.

I use this workflow myself to stay informed on AI and automation topics without the overload of daily news or the delayed delivery caused by a fixed newsletter schedule.

You can download the workflow here.

Want a hosted version without running your own n8n? Contact me here.