<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Jayanth</title>
    <description>The latest articles on Forem by Jayanth (@grewup).</description>
    <link>https://forem.com/grewup</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3861039%2Fed96fe23-36a0-4d4e-a489-d65e99f6470f.jpeg</url>
      <title>Forem: Jayanth</title>
      <link>https://forem.com/grewup</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/grewup"/>
    <language>en</language>
    <item>
      <title>I Built an n8n Workflow That Generates “Top 10” Videos Automatically (System Design + Lessons)</title>
      <dc:creator>Jayanth</dc:creator>
      <pubDate>Wed, 29 Apr 2026 13:45:23 +0000</pubDate>
      <link>https://forem.com/grewup/i-built-an-n8n-workflow-that-generates-top-10-videos-automatically-system-design-lessons-2jic</link>
      <guid>https://forem.com/grewup/i-built-an-n8n-workflow-that-generates-top-10-videos-automatically-system-design-lessons-2jic</guid>
      <description>&lt;p&gt;&lt;strong&gt;Why I built this&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Creating content manually doesn’t scale.&lt;/p&gt;

&lt;p&gt;Even a simple “Top 10” video takes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;research&lt;/li&gt;
&lt;li&gt;scripting&lt;/li&gt;
&lt;li&gt;editing&lt;/li&gt;
&lt;li&gt;uploading&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Repeat that daily → it becomes a bottleneck.&lt;/p&gt;

&lt;p&gt;So instead of optimizing the process…&lt;/p&gt;

&lt;p&gt;I removed myself from it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this system does&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This workflow automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;generates topics&lt;/li&gt;
&lt;li&gt;writes structured scripts&lt;/li&gt;
&lt;li&gt;creates voiceovers&lt;/li&gt;
&lt;li&gt;generates visuals&lt;/li&gt;
&lt;li&gt;assembles final videos&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Runs on a schedule.&lt;/p&gt;

&lt;p&gt;No manual input.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`Scheduler (cron)
↓
Topic Generator (AI)
↓
Script Generator (Top 10 format)
↓
Voice Generation (TTS)
↓
Visual Generation
↓
Video Assembly
↓
Upload / Storage`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This type of pipeline is possible because :contentReference[oaicite:0]{index=0} connects APIs and services into deterministic workflows that execute predefined steps reliably. :contentReference[oaicite:1]{index=1}  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1 — Trigger (Scheduler)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cron node runs daily&lt;/li&gt;
&lt;li&gt;Starts entire pipeline automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — Topic Generation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AI generates topics like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Top 10 AI tools”&lt;/li&gt;
&lt;li&gt;“Top 10 productivity apps”&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Insight:
&lt;/h3&gt;

&lt;p&gt;Topic quality = performance&lt;/p&gt;

&lt;p&gt;Bad topic → system still runs → bad output at scale&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — Script Generation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Strict structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hook&lt;/li&gt;
&lt;li&gt;Intro&lt;/li&gt;
&lt;li&gt;Ranked list (#10 → #1)&lt;/li&gt;
&lt;li&gt;Outro&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
txt
Top 10 AI tools you need in 2026

#10 ...
#9 ...
...
Why this matters

Structured formats are predictable → perfect for automation


**Step 4 — Voice Generation**
Text → speech (TTS / cloned voice)
Short sentences only
Common mistake

Long paragraphs → unnatural audio → low retention


**Step 5 — Visual Generation**

For each list item:

generate image / clip
map to script section


**Step 6 — Video Assembly**

Combine:

audio
visuals
timing

Output = ready-to-publish video


**Step 7 — Upload / Storage**
YouTube / Drive / S3
store metadata (URL, topic, status)
What actually breaks (real issues)

**1. Garbage input → garbage output**

Automation scales mistakes.

If topics are weak:
→ system produces low-performing videos faster

**2. Generic AI scripts**

Without constraints:
→ repetitive + boring content

Fix:

enforce structure
control output length

**3. Timing + pacing issues**
long segments kill engagement
uneven pacing breaks flow

**4. System maintenance**

n8n workflows are powerful but require setup and maintenance as integrations evolve.


**Key insight**

This is NOT a video generation system.

It’s a content pipeline.

And the difference is:

Manual:
idea → create → edit → publish

System:
data → process → output → repeat


**Why this works**

“Top 10” content has:

fixed structure
repeatable format
predictable output

Which makes it ideal for automation.


**Final workflow (simplified)**
Idea
 → Generate script
 → Generate voice
 → Generate visuals
 → Assemble video
 → Publish


**Final thought**

Most people try to:

edit faster
script better
produce more

But the real leverage is:

Designing systems that produce content without you


**Question for devs**

Anyone here building automated content pipelines?

Curious about:

scaling workflows
handling quality control
avoiding repetitive outputs

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>ai</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Automate UGC Ad Videos with AI (Full Workflow + System Design)</title>
      <dc:creator>Jayanth</dc:creator>
      <pubDate>Tue, 28 Apr 2026 16:54:45 +0000</pubDate>
      <link>https://forem.com/grewup/automate-ugc-ad-videos-with-ai-full-workflow-system-design-4ilm</link>
      <guid>https://forem.com/grewup/automate-ugc-ad-videos-with-ai-full-workflow-system-design-4ilm</guid>
      <description>&lt;p&gt;&lt;strong&gt;What this builds&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An automated pipeline that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;generates ad scripts&lt;/li&gt;
&lt;li&gt;creates voice + video&lt;/li&gt;
&lt;li&gt;outputs ready-to-use UGC ads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Architecture&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`Input Idea
↓
AI Script Generator
↓
Voice Generation
↓
Video Generation
↓
Editing Layer
↓
Final Output`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 1 — Script Generation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use LLM (Gemini / GPT)&lt;/p&gt;

&lt;p&gt;Structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hook&lt;/li&gt;
&lt;li&gt;Problem&lt;/li&gt;
&lt;li&gt;Solution&lt;/li&gt;
&lt;li&gt;CTA&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — Voice Layer&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TTS / cloned voice&lt;/li&gt;
&lt;li&gt;keep sentences short&lt;/li&gt;
&lt;li&gt;natural tone&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — Video Layer&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;avatar or stock visuals&lt;/li&gt;
&lt;li&gt;sync with audio&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 4 — Editing Layer&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;captions&lt;/li&gt;
&lt;li&gt;transitions&lt;/li&gt;
&lt;li&gt;pacing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What breaks&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Long scripts&lt;br&gt;
→ poor pacing&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Generic output&lt;br&gt;
→ low engagement&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No structure&lt;br&gt;
→ unusable video&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Key insight&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is not video generation.&lt;/p&gt;

&lt;p&gt;It’s &lt;strong&gt;content pipeline engineering&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Full breakdown with screenshot check here - &lt;a href="https://elevoras.com/ugc-ads-2025-automate-videos-with-ai-save-100-hours/" rel="noopener noreferrer"&gt;UGC Ads 2025: Automate Videos With AI (Save 100+ Hours)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use cases&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ad creatives&lt;/li&gt;
&lt;li&gt;content agencies&lt;/li&gt;
&lt;li&gt;social media automation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Question&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Anyone here using AI for ad creatives at scale?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Automate Blog Publishing with n8n + AI + WordPress (Full Workflow) published: true</title>
      <dc:creator>Jayanth</dc:creator>
      <pubDate>Mon, 27 Apr 2026 14:31:45 +0000</pubDate>
      <link>https://forem.com/grewup/automate-blog-publishing-with-n8n-ai-wordpress-full-workflowpublished-true-56cl</link>
      <guid>https://forem.com/grewup/automate-blog-publishing-with-n8n-ai-wordpress-full-workflowpublished-true-56cl</guid>
      <description>&lt;p&gt;&lt;strong&gt;What this builds&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A complete automated blog pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fetch trending content&lt;/li&gt;
&lt;li&gt;generate AI articles&lt;/li&gt;
&lt;li&gt;create images&lt;/li&gt;
&lt;li&gt;publish to WordPress&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Architecture&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`Scheduler (RSS)
↓
Filter topics
↓
AI content generation
↓
Image generation
↓
WordPress publish
↓
Tracking`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 1 — Fetch content (RSS)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pull from news sources&lt;/li&gt;
&lt;li&gt;filter last 24 hours&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — Topic filtering&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;remove irrelevant content&lt;/li&gt;
&lt;li&gt;keep niche-specific topics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — AI blog generation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Generate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;title&lt;/li&gt;
&lt;li&gt;intro&lt;/li&gt;
&lt;li&gt;structured body&lt;/li&gt;
&lt;li&gt;conclusion&lt;/li&gt;
&lt;li&gt;SEO metadata&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 4 — Image generation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;generate featured image&lt;/li&gt;
&lt;li&gt;attach to post&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 5 — Publish via WordPress API&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;POST request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
&lt;code&gt;json&lt;br&gt;
{&lt;br&gt;
  "title": "Generated Title",&lt;br&gt;
  "content": "AI content",&lt;br&gt;
  "status": "publish"&lt;br&gt;
}&lt;/code&gt;&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;



**Step 6 — Tracking layer**

Store:

topic
publish date
URL


**What breaks**
1. Generic content

Fix: enforce structure

2. Duplicate posts

Fix: tracking layer

3. SEO issues

Fix: add metadata + structure

For full breakdown with screenshot and workflow template check out [Automate Your Blog with AI : The Complete N8N News-to-WordPress Tutorial](https://elevoras.com/automate-your-blog-with-ai-the-complete-n8n-news-to-wordpress-tutorial/)

**Key insight**

This is not content generation.

This is content infrastructure.


**Use cases**

- niche blogs

- affiliate sites



**Question**

Anyone running AI content pipelines in production?

Curious about scaling + SEO impact.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>ai</category>
      <category>n8nbrightdatachallenge</category>
      <category>automation</category>
    </item>
    <item>
      <title>Automate Instagram Comments with n8n + AI (Full Workflow + Real Issues)</title>
      <dc:creator>Jayanth</dc:creator>
      <pubDate>Sun, 26 Apr 2026 11:03:16 +0000</pubDate>
      <link>https://forem.com/grewup/automate-instagram-comments-with-n8n-ai-full-workflow-real-issues-57c9</link>
      <guid>https://forem.com/grewup/automate-instagram-comments-with-n8n-ai-full-workflow-real-issues-57c9</guid>
      <description>&lt;p&gt;&lt;strong&gt;What this builds&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An automated system that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;monitors Instagram comments&lt;/li&gt;
&lt;li&gt;generates contextual AI replies&lt;/li&gt;
&lt;li&gt;optionally sends DMs&lt;/li&gt;
&lt;li&gt;tracks everything&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Basically:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;comment → response → conversation → lead&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`Instagram Comment Trigger
↓
Fetch Post + Comment Data
↓
Filter (duplicates / spam)
↓
AI Response Generator
↓
Post Reply (Graph API)
↓
Send DM (optional)
↓
Store in DB / Google Sheets`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 1 — Trigger (Monitor Comments)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use Instagram Graph API.&lt;/p&gt;

&lt;p&gt;Setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;poll every 2–5 minutes&lt;/li&gt;
&lt;li&gt;fetch latest posts&lt;/li&gt;
&lt;li&gt;extract new comments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why polling?&lt;/p&gt;

&lt;p&gt;Because real-time webhook setup is more complex and not required for most use cases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — Filtering Layer (Critical)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before doing anything, filter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;already replied comments&lt;/li&gt;
&lt;li&gt;duplicate users&lt;/li&gt;
&lt;li&gt;spam comments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you skip this:&lt;br&gt;
→ your workflow will reply multiple times to the same user&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — Extract Context&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;From each comment, extract:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;comment_text&lt;/span&gt;
&lt;span class="nx"&gt;username&lt;/span&gt;
&lt;span class="nx"&gt;post_caption&lt;/span&gt;

&lt;span class="nx"&gt;Why&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="nx"&gt;matters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="nx"&gt;AI&lt;/span&gt; &lt;span class="nx"&gt;without&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;generic&lt;/span&gt; &lt;span class="nx"&gt;replies&lt;/span&gt;
&lt;span class="nx"&gt;AI&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;relevant&lt;/span&gt; &lt;span class="nx"&gt;replies&lt;/span&gt;


&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;Step&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="nx"&gt;AI&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt; &lt;span class="nx"&gt;Generation&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;

&lt;span class="nx"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;{{ comment_text + post_caption }}&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
System prompt example:

You are a helpful assistant replying to Instagram comments.

- Keep replies under 2 sentences
- Be natural and conversational
- Avoid generic responses
- Match context of the post

Output:

short reply
human-like tone
contextual response


**Step 5 — Post Reply**

Use Facebook Graph API:

POST /{comment_id}/replies

Payload:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;{&lt;br&gt;
  "message": "AI generated reply"&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

This posts directly under the comment.


**Step 6 — DM Automation (Optional but Powerful)**

Trigger DMs based on:

keywords (e.g. "price", "link")
or every comment

Examples:

send product link
send lead magnet
send onboarding message

This is where conversions actually happen.


**Step 7 — Tracking Layer**

Store data in:

Google Sheets
Airtable
Database

Track:

username
comment
reply_sent
dm_sent
timestamp

Why?

prevents duplicates
builds lead database
helps debugging


**What breaks (real issues)**
1. Duplicate replies

Cause:

no tracking

Fix:

always store processed comments
2. Generic AI responses

Cause:

no context

Fix:

include post caption + intent
3. API limits

Instagram Graph API has:

rate limits
permission restrictions

Fix:

throttle requests
batch processing
4. Delay vs real-time

Polling = delay (2–5 min)

Tradeoff:

simpler setup
good enough for most use cases


**Key insight**

Most people think this is an "automation problem".

It’s not.

It’s a context + control problem.

Bad input → bad replies → low engagement


**Where this is useful**
creators (engagement boost)
agencies (lead generation)
businesses (auto support)


**Final workflow (simplified)**
Comment
 → Filter
 → Generate reply
 → Post reply
 → Send DM
 → Store data


Anyone here running Instagram automation at scale?

Curious about:

rate limit handling
better DM logic
spam avoidance strategies

Drop your setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>ai</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Build a Voice Cloning Auto-Reply Bot with n8n + ElevenLabs (Real Workflow, Not Theory)</title>
      <dc:creator>Jayanth</dc:creator>
      <pubDate>Sat, 25 Apr 2026 17:27:22 +0000</pubDate>
      <link>https://forem.com/grewup/build-a-voice-cloning-auto-reply-bot-with-n8n-elevenlabs-real-workflow-not-theory-2n26</link>
      <guid>https://forem.com/grewup/build-a-voice-cloning-auto-reply-bot-with-n8n-elevenlabs-real-workflow-not-theory-2n26</guid>
      <description>&lt;p&gt;Most “&lt;strong&gt;AI voice bot&lt;/strong&gt;” tutorials show the result.&lt;/p&gt;

&lt;p&gt;Very few show what actually breaks when you try to build one.&lt;/p&gt;

&lt;p&gt;This is a full working workflow using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;n8n (automation)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ElevenLabs (voice cloning)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AI model (response generation)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And more importantly , what you need to get right for it to actually work in real use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we’re building&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A system that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Receives a message&lt;/li&gt;
&lt;li&gt;Generates a contextual AI response&lt;/li&gt;
&lt;li&gt;Converts it into a cloned voice&lt;/li&gt;
&lt;li&gt;Sends the audio back automatically&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;customer support automation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;voice assistants&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;creator voice replies&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;agency workflows&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Architecture&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`Input (Webhook / App)
        ↓
AI Response (LLM)
        ↓
ElevenLabs (Text → Speech)
        ↓
Output (API / App / Messaging)`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 1 — Webhook (Input Layer)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In n8n:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Add a Webhook node&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enable POST requests&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example input:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Tell me about your service"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why webhook?&lt;/p&gt;

&lt;p&gt;Because it allows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;app integrations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;API triggers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;scalable input&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — AI Response Layer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add an AI node (OpenAI / OpenRouter).&lt;/p&gt;

&lt;p&gt;Input:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jinja"&gt;&lt;code&gt;`&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nv"&gt;json.message&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;System prompt (critical)&lt;/p&gt;

&lt;p&gt;This is where most people mess up.&lt;/p&gt;

&lt;p&gt;Bad:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;long paragraphs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;generic responses&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;no tone control&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Good:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;short replies&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;defined tone&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;controlled output&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`You are a helpful assistant.

- Keep responses under 3 sentences
- Use natural conversational tone
- Avoid long explanations`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This directly affects voice quality later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — ElevenLabs Voice Generation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use:&lt;/p&gt;

&lt;p&gt;HTTP Request node&lt;br&gt;
OR&lt;br&gt;
dedicated integration&lt;br&gt;
API structure (simplified):&lt;br&gt;
POST &lt;a href="https://api.elevenlabs.io/v1/text-to-speech/%7Bvoice_id%7D" rel="noopener noreferrer"&gt;https://api.elevenlabs.io/v1/text-to-speech/{voice_id}&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{ $json.output }}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eleven_multilingual_v2"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Headers:&lt;/p&gt;

&lt;p&gt;xi-api-key&lt;br&gt;
content-type&lt;/p&gt;

&lt;p&gt;Output:&lt;/p&gt;

&lt;p&gt;audio stream / file&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4 — Output Layer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Options depend on use case:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;return audio via API&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;send via WhatsApp / Telegram&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;attach in app&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;auto reply system&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example (basic API response):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"audio_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"generated_audio.mp3"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Full Workflow Logic&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Webhook&lt;br&gt;
 → AI Node&lt;br&gt;
 → ElevenLabs&lt;br&gt;
 → Response&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What actually breaks (real issues)&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Long AI responses = bad audio&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Problem:&lt;/p&gt;

&lt;p&gt;sounds robotic&lt;br&gt;
unnatural pacing&lt;/p&gt;

&lt;p&gt;Fix:&lt;/p&gt;

&lt;p&gt;limit output length&lt;br&gt;
enforce short responses&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Latency issues&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Flow delay:&lt;/p&gt;

&lt;p&gt;AI response&lt;br&gt;
voice generation&lt;/p&gt;

&lt;p&gt;Result:&lt;/p&gt;

&lt;p&gt;slow replies&lt;/p&gt;

&lt;p&gt;Fix:&lt;/p&gt;

&lt;p&gt;reduce token usage&lt;br&gt;
optimize prompts&lt;br&gt;
avoid unnecessary steps&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Voice quality problems&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Common issues:&lt;/p&gt;

&lt;p&gt;inconsistent tone&lt;br&gt;
unnatural pauses&lt;/p&gt;

&lt;p&gt;Fix:&lt;/p&gt;

&lt;p&gt;use clean training data&lt;br&gt;
adjust ElevenLabs settings&lt;br&gt;
test multiple voice configs&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cost scaling&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You’re paying for:&lt;/p&gt;

&lt;p&gt;AI tokens&lt;br&gt;
voice generation&lt;/p&gt;

&lt;p&gt;Bad setup:&lt;/p&gt;

&lt;p&gt;long responses → higher cost&lt;/p&gt;

&lt;p&gt;Good setup:&lt;/p&gt;

&lt;p&gt;short + precise outputs&lt;br&gt;
Important design insight&lt;/p&gt;

&lt;p&gt;Most people think this is a “voice problem”.&lt;/p&gt;

&lt;p&gt;It’s not.&lt;/p&gt;

&lt;p&gt;It’s a text control problem.&lt;/p&gt;

&lt;p&gt;If your text output is bad:&lt;br&gt;
→ your voice output will be worse&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where this becomes powerful&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once stable, you can extend this to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Multi-language voice bots&lt;/li&gt;
&lt;li&gt;memory-based assistants&lt;/li&gt;
&lt;li&gt;CRM integrations&lt;/li&gt;
&lt;li&gt;lead qualification systems&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Final thoughts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This workflow is simple on paper:&lt;/p&gt;

&lt;p&gt;Text → AI → Voice → Output&lt;/p&gt;

&lt;p&gt;But the quality depends entirely on:&lt;/p&gt;

&lt;p&gt;how you control responses&lt;br&gt;
how you handle latency&lt;br&gt;
how you structure the flow&lt;/p&gt;

&lt;p&gt;The difference between a demo and a usable system is in these details.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you want the full breakdown check out - &lt;a href="https://elevoras.com/build-voice-clone-bot-n8n-elevenlabs-automation-2026/" rel="noopener noreferrer"&gt;Build Voice Clone Bot : n8n + ElevenLabs Automation 2026&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Question&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Anyone here running voice-based automation in production?&lt;/p&gt;

&lt;p&gt;Curious how you’re handling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;latency&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;scaling&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;real-time responses&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Would love to compare setups 👇&lt;/p&gt;

</description>
      <category>ai</category>
      <category>tutorial</category>
      <category>beginners</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Emergent AI Pricing Explained Credits, Plans &amp; How Not to Waste Money</title>
      <dc:creator>Jayanth</dc:creator>
      <pubDate>Fri, 24 Apr 2026 14:22:54 +0000</pubDate>
      <link>https://forem.com/grewup/emergent-ai-pricing-explained-credits-plans-how-not-to-waste-money-ddc</link>
      <guid>https://forem.com/grewup/emergent-ai-pricing-explained-credits-plans-how-not-to-waste-money-ddc</guid>
      <description>&lt;p&gt;&lt;strong&gt;The credit problem nobody warns you about&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're coming to Emergent from flat-rate tools — GitHub Copilot, Cursor, Replit's base tier — the pricing model will catch you off guard.&lt;br&gt;
Emergent isn't $20/month = unlimited. It's $20/month = 100 credits. And credits are consumed every time the AI does work: generating a feature, debugging an error, refactoring a component, loading project context.&lt;br&gt;
There's no upfront price list. No console that tells you "this prompt will cost 18 credits." You build, you spend, you find out at the end.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here's the credit cost map I put together from actual usage:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`TASK                              CREDIT COST
─────────────────────────────────────────────
Landing page + contact form       10–20 credits
User authentication (OAuth)       25–40 credits
Multi-file debugging session      15–30 credits
Full styling refactor             30–50 credits
Stripe payment integration        35–60 credits`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ranges exist because detailed prompts cost more than vague ones. "Add login" costs less than "Add login with Google OAuth, session management, error states, and post-auth redirect." Precision in your prompt = more computation = more credits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where credits actually disappear&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Context loading scales with project size. Emergent reads your entire project before making changes — this is what enables it to modify multi-file logic correctly. But as your codebase grows, every action costs more because context is larger.&lt;/p&gt;

&lt;p&gt;Debug loops are the silent budget drain. Identify bug → generate fix → test → secondary error → fix again. That's 30–50 credits per loop. Multiple loops on a complex bug can cost as much as building the original feature.&lt;/p&gt;

&lt;p&gt;"Quick changes" aren't quick credit-wise. Asking for a small tweak on a large project still triggers full project context loading. There's no "lightweight mode."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The math that matters:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Standard breaks even with Pro at ~150 credits/month. Above that, Pro saves money per credit.&lt;br&gt;
Team only makes sense for genuine multi-developer workflows. Solo builders pay ~$100/month for collaboration features they don't use.&lt;br&gt;
Unused credits expire monthly. Don't over-subscribe. Start at Standard, track for 30 days, then decide.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The decision framework in code&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="s2"&gt;`function pickEmergentPlan(monthlyCredits, teamSize) {
  if (teamSize &amp;gt;= 3) return "Team";
  if (monthlyCredits &amp;gt; 150) return "Pro";
  if (monthlyCredits &amp;gt; 0) return "Standard";
  return "Free (demo only)";
}

// Add 30% buffer for debugging
const estimatedCredits = featureCost * 1.3;
const plan = pickEmergentPlan(estimatedCredits, 1);`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Discount code&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before you pick a plan: ELEVORAS at checkout = 5% off Standard, Pro, and Team. Applies every billing cycle as long as your subscription is active. Cancel and resubscribe = need to re-enter.&lt;/p&gt;

&lt;p&gt;vs. alternatives (developer perspective)&lt;br&gt;
Replit: Better community, better for projects requiring real developer control. "Unlimited" throttles on heavy compute. Real cost at serious usage: $50+/month.&lt;/p&gt;

&lt;p&gt;Bolt: No-credit flat rate is good for prototyping at volume. Lock-in is the problem — exporting a Bolt app to your own infra requires refactoring. Plan for that time cost.&lt;br&gt;
Lovable: Superior reasoning for complex logic. $40/month for 200 credits. Worth it for production-grade apps where correctness matters more than speed.&lt;/p&gt;

&lt;p&gt;Emergent's actual advantage for developers: Exported codebase is clean, readable, and deployable anywhere. No proprietary runtime. You own what you build.&lt;br&gt;
Full breakdown + discount code at elevoras.com — link in bio.&lt;br&gt;
Drop questions about specific use case credit costs below — happy to share the estimates.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Build a 24/7 Facebook Messenger AI Chatbot With n8n (Full Setup — No Code)</title>
      <dc:creator>Jayanth</dc:creator>
      <pubDate>Wed, 22 Apr 2026 09:17:25 +0000</pubDate>
      <link>https://forem.com/grewup/build-a-247-facebook-messenger-ai-chatbot-with-n8n-full-setup-no-code-5fg9</link>
      <guid>https://forem.com/grewup/build-a-247-facebook-messenger-ai-chatbot-with-n8n-full-setup-no-code-5fg9</guid>
      <description>&lt;p&gt;&lt;strong&gt;What this builds&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A Facebook Messenger chatbot that responds to every incoming message automatically, 24/7, using an AI agent connected to your Facebook Business Page via n8n. Customers message your page — they receive an intelligent, context-aware reply within 30 seconds, whether you are online or not.&lt;/p&gt;

&lt;p&gt;Workflow JSON download: Available on the blog&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`Facebook Messenger message received
        ↓
n8n Webhook (GET + POST — allow multiple HTTP methods)
        ↓
IF node — hub.mode = "subscribe" AND hub.verify_token = [your token]
  ├── TRUE → Respond to Webhook with hub.challenge (verification)
  └── FALSE → Extract message from POST body
        ↓
AI Agent node (OpenRouter)
  — reads sender ID + message text
  — generates contextual reply
        ↓
HTTP Request → Facebook Graph API
  POST /me/messages → send reply to sender`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 1 — Create your Facebook Developer App&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Go to developers.facebook.com and create an account if you do not have one.&lt;br&gt;
Click My Apps → Create App.&lt;/p&gt;

&lt;p&gt;App configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`App type:    Business
App name:    [Your Business] AI Assistant
Portfolio:   Select yours, or "No business portfolio selected"`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Important: Do not overthink the naming. You can modify everything later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — Switch app to Live mode (most tutorials skip this)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your app starts in Development mode — only you can test it. Real customers cannot interact with it until you switch to Live.&lt;br&gt;
Before switching, you need a Privacy Policy URL. Facebook verifies this automatically.&lt;br&gt;
Fastest method: go to termsfeed.com → generate a free privacy policy → copy the URL.&lt;br&gt;
In your app settings:&lt;/p&gt;

&lt;p&gt;Find "Privacy Policy URL" field → paste your URL&lt;br&gt;
Toggle the mode switch at the top from Development to Live&lt;/p&gt;

&lt;p&gt;Without this step, your chatbot will only work when you are testing it yourself. All live customer interactions will fail silently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — Create the n8n webhook&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Open your n8n dashboard → New Workflow → add a Webhook trigger node.&lt;br&gt;
Critical settings in the webhook configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`Allow Multiple HTTP Methods: ON  ← most people miss this
HTTP Method:                 GET and POST both selected
Respond:                     Using 'Respond to Webhook' Node`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The "Allow Multiple HTTP Methods" toggle is the setting that allows Facebook's verification handshake (GET request) and actual message delivery (POST request) to both hit the same webhook endpoint. Without this, you need two separate webhooks — and the verification fails.&lt;br&gt;
Copy the Production URL from the webhook node. This is what you paste into Facebook next.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4 — Add Messenger to your Facebook App&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Back in your Facebook Developer dashboard:&lt;/p&gt;

&lt;p&gt;Find the Products section → click Add Product&lt;br&gt;
Find Messenger → click Set up&lt;/p&gt;

&lt;p&gt;Inside the Messenger configuration:&lt;br&gt;
Webhook settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`Callback URL:   [paste your n8n Production URL]
Verify Token:   AI-chatbot  (or any string you choose — you will need this exactly in Step 5)`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Click Verify and Save. Facebook sends a GET request to your webhook to confirm it is reachable.&lt;br&gt;
Subscribe to message events:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`messages:        ON
message_reads:   ON`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate a Page Access Token:&lt;/p&gt;

&lt;p&gt;Select your Business Page from the dropdown&lt;br&gt;
Click Generate Token&lt;br&gt;
Copy the token immediately — it only displays once&lt;br&gt;
Store it securely — you need it in Step 7 to send messages back&lt;/p&gt;

&lt;p&gt;Subscribe your page to the webhook by clicking Add Subscriptions next to your page name.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5 — The verification handshake (where 90% of people get stuck)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Facebook verifies your webhook by sending a GET request with three query parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`hub.mode          = "subscribe"
hub.verify_token  = [the token you set in Facebook]
hub.challenge     = [a random string Facebook wants echoed back]`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your n8n workflow must respond with the exact value of hub.challenge. If it does not, Facebook rejects the webhook and nothing works.&lt;br&gt;
Add an IF node after the webhook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`Condition 1: {{ $json.query['hub.mode'] }}         equals  subscribe
Condition 2: {{ $json.query['hub.verify_token'] }}  equals  AI-chatbot
Both conditions must be TRUE`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the TRUE branch — add a "Respond to Webhook" node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`Respond with:  Text
Response body: [switch from Fixed to Expression]
Expression:    {{ $json.query['hub.challenge'] }}`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This echoes the challenge string back to Facebook, completing verification. Until this works, nothing else in the workflow can run.&lt;br&gt;
Test it: Save the workflow, click Execute Workflow, then go back to Facebook and click Verify and Save on the webhook configuration. If verification fails, check that your IF conditions exactly match the parameters Facebook sends.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 6 — Extract the message from POST requests&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a customer sends a message, Facebook sends a POST request to your webhook with a JSON body. The message is nested inside:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;`//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Facebook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Messenger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;webhook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;POST&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;structure&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"entry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"messaging"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sender"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SENDER_PAGE_SCOPED_ID"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"recipient"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YOUR_PAGE_ID"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1234567890&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"mid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"message_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello, is this available?"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the FALSE branch of your IF node (POST requests that are not verification), add a Set Fields node to extract what you need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sender_id:    {{ $json.body.entry[0].messaging[0].sender.id }}
message_text: {{ $json.body.entry[0].messaging[0].message.text }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These two fields feed into the AI agent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 7 — AI Agent node&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add an AI Agent node. Connect OpenRouter credentials.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`Input:        {{ $json.message_text }}`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;System prompt — paste this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`You are a helpful customer service assistant for [Your Business Name].

You respond to Facebook Messenger messages from potential customers 
and existing clients.

Your tone: friendly, professional, helpful. Never robotic.

Key information about our business:
[Add: what you sell, your pricing if public, your hours, 
your location, your main services — whatever customers typically ask about]

If you cannot answer a specific question, tell the customer:
"I'll make sure [Name] gets back to you on this personally — 
usually within a few hours during business hours."

Never make up information you are not sure about.
Keep replies concise — 2-4 sentences maximum.`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Customise the system prompt for your specific business. The more specific the business information, the more useful the responses. Add your FAQs, your pricing tier (if public), your response time commitments, and any questions you regularly receive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 8 — Send the reply via Facebook Graph API&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;`&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`json&lt;br&gt;
Add an HTTP Request node.&lt;/p&gt;

&lt;p&gt;Method:  POST&lt;br&gt;
URL:     &lt;a href="https://graph.facebook.com/v18.0/me/messages" rel="noopener noreferrer"&gt;https://graph.facebook.com/v18.0/me/messages&lt;/a&gt;&lt;br&gt;
Headers:&lt;br&gt;
  Authorization:  Bearer [YOUR_PAGE_ACCESS_TOKEN]&lt;br&gt;
  Content-Type:   application/json&lt;/p&gt;

&lt;p&gt;Body (JSON):&lt;br&gt;
{&lt;br&gt;
  "recipient": {&lt;br&gt;
    "id": "{{ $('Set Fields').first().json.sender_id }}"&lt;br&gt;
  },&lt;br&gt;
  "message": {&lt;br&gt;
    "text": "{{ $json.output }}"&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;Replace [YOUR_PAGE_ACCESS_TOKEN] with the token you generated in Step 4.&lt;br&gt;
The sender_id targets the reply to the specific person who sent the message. The $json.output contains the AI agent's response.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 9 — Activate and test&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Toggle the workflow Active (top right — turns blue).&lt;br&gt;
Open your Facebook Business Page. Send a message to your own page from a personal Facebook account. Wait 15–30 seconds. You should receive an AI-generated reply.&lt;br&gt;
If no reply arrives, check the Executions tab in n8n. Each execution log shows exactly which node completed and where the flow stopped.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What breaks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Facebook verification fails repeatedly: Your IF node conditions are not matching Facebook's query parameters exactly. The parameters are case-sensitive: hub.mode and hub.verify_token with dots, not underscores. Also check that your Respond to Webhook node is on the TRUE branch, not the FALSE branch.&lt;/p&gt;

&lt;p&gt;Messages arrive but no reply is sent: The Page Access Token has expired or is invalid. Facebook page tokens can expire — regenerate it from the Facebook Developer dashboard and update the HTTP Request node. Also confirm the token has pages_messaging permission.&lt;/p&gt;

&lt;p&gt;AI Agent responds but Graph API returns 403: Your app does not have the pages_messaging permission enabled. Go to App Settings → Permissions and Features → search pages_messaging → Request permission.&lt;br&gt;
Workflow triggers on your own page posts, not just messages: You subscribed to more events than needed. In Facebook Messenger webhook settings, ensure only messages and message_reads are enabled. Disable feed, comments, and other event types.&lt;/p&gt;

&lt;p&gt;n8n receives POST but entry[0].messaging is undefined: Facebook occasionally sends other webhook event types (delivery receipts, echo messages). Add a second IF condition before the AI agent: check that $json.body.entry[0].messaging[0].message.text exists and is not null.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Running cost&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;OpenRouter with GPT-4 mini: approximately $0.001 per message response. 1,000 customer messages per month costs about $1.00 in API credits.&lt;br&gt;
n8n: free self-hosted or free cloud trial.&lt;br&gt;
Facebook API: free.&lt;/p&gt;

&lt;p&gt;The entire chatbot infrastructure costs under $7/month for most small businesses — significantly less than a single missed client.&lt;br&gt;
Workflow JSON at elevoras.com. Import directly into n8n and update the Page Access Token and verify token to your own values.&lt;/p&gt;

&lt;p&gt;Building a variation with memory (tracking conversation history per user)? Drop it in the comments — that is the natural next step and happy to share the memory node setup.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>tutorial</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Automate Personalized Cold Email Icebreakers With n8n, 0.3% to 10% Reply Rate</title>
      <dc:creator>Jayanth</dc:creator>
      <pubDate>Tue, 21 Apr 2026 09:43:30 +0000</pubDate>
      <link>https://forem.com/grewup/automate-personalized-cold-email-icebreakers-with-n8n-03-to-10-reply-rate-48pk</link>
      <guid>https://forem.com/grewup/automate-personalized-cold-email-icebreakers-with-n8n-03-to-10-reply-rate-48pk</guid>
      <description>&lt;p&gt;&lt;strong&gt;What this builds&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An n8n workflow that takes a list of prospects from Google Sheets, scrapes each prospect's website (homepage plus up to 3 internal pages), uses AI to extract meaningful business insights from each page, generates a hyper-personalised multi-line icebreaker for each prospect, and writes the icebreaker back to the Sheet — ready for your outreach tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Output example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`"Hey Katie, love how KTL Graphics makes it easy to filter by acreage — 
also a fan of your property update email option. Wanted to run 
something by you..."`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The system found acreage filtering and email notification features by actually crawling the website — not from the company name or LinkedIn headline.&lt;/p&gt;

&lt;p&gt;Workflow JSON download: Available on the blog&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`Manual Trigger
        ↓
Google Sheets — get all rows
        ↓
Filter — only rows with email AND website URL
        ↓
Loop Over Items (batch size 1)
        ↓
HTTP Request — scrape homepage (HTML)
        ↓
Edit Fields — extract html field
        ↓
Code node — convert to string
        ↓
HTML Extractor — pull all &amp;lt;a href&amp;gt; links
        ↓
Edit Fields — keep: first_name, last_name, email, website_url, links
        ↓
Split Out — one row per link
        ↓
Filter — links starting with /
        ↓
Code node — normalise relative/absolute URLs
        ↓
Remove Duplicates + Limit (max 3 pages)
        ↓
HTTP Request — fetch each internal page
        ↓
HTML to Markdown conversion
        ↓
AI Agent — summarise each page into abstract
        ↓
Merge all abstracts
        ↓
AI Agent — generate icebreaker from all abstracts
        ↓
Google Sheets — write icebreaker back to prospect row`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 1 — Google Sheets setup&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Export your Apollo.io leads (or any source) to Google Sheets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Required columns:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csvs"&gt;&lt;code&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="k"&gt;first&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;name&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;last&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;name&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;email&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;website&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;url&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="k"&gt;icebreaker&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;filled&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="k"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The workflow reads from this sheet and writes icebreakers back to the icebreaker column.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — Filter node (quality gate)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add a Filter node after the Sheets Get Rows node.&lt;br&gt;
Two conditions, both must be true:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`Condition 1: website_url  exists and is not empty
Condition 2: email        exists and is not empty`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without this filter, the workflow attempts to scrape blank URLs and throws errors that cascade through the rest of the pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — Loop Over Items (batch size 1)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add a Loop Over Items node.&lt;/p&gt;

&lt;p&gt;Batch Size: 1&lt;/p&gt;

&lt;p&gt;This processes one prospect at a time. Without it, the workflow tries to process all prospects simultaneously — rate limits on external sites cause failures, and the AI responses get mixed across prospects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4 — Scrape the homepage&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add an HTTP Request node.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`Method:      GET
URL:         {{ $json.website_url }}
Error handling: Continue on error
Redirects:   Follow, max 21`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"Continue on error" is critical. Some websites block scraping. Without this setting, one blocked site kills the entire workflow run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5 — Extract and normalise HTML&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add an Edit Fields node:&lt;/p&gt;

&lt;p&gt;Field: html → string → {{ $json.data }}&lt;/p&gt;

&lt;p&gt;Add a Code node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="s2"&gt;`return [{
  json: {
    html: $json.html.toString()
  }
}];`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This converts the raw response body to a usable string&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 6 — Extract all links from the homepage&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add an HTML Extractor node.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`CSS Selector:  a
Attribute:     href
Return:        Array
Options:       Trim values + clean text`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pulls every link from the homepage — navigation, footer, internal pages. The homepage alone contains only part of the story. The About page, Services page, and Blog posts are where the personalisation gold lives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 7 — Normalise URLs (Code node)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After splitting links into individual rows and filtering for links starting with /, add this Code node to normalise both relative and absolute URLs to relative paths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="s2"&gt;`const items = $input.all();

const updatedItems = items.map((item) =&amp;gt; {
  const link = item?.json?.links;

  if (typeof link === "string") {
    if (link.startsWith("/")) {
      item.json.links = link;
    }
    else if (link.startsWith("http://") || link.startsWith("https://")) {
      try {
        const url = new URL(link);
        let path = url.pathname;
        if (path !== "/" &amp;amp;&amp;amp; path.endsWith("/")) {
          path = path.slice(0, -1);
        }
        item.json.links = path || "/";
      } catch (e) {
        item.json.links = link;
      }
    }
    else {
      item.json.links = link;
    }
  }
  return item;
});

return updatedItems;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why this is necessary: Websites use both relative links (/about) and absolute links (&lt;a href="https://example.com/about" rel="noopener noreferrer"&gt;https://example.com/about&lt;/a&gt;). You need both normalised to the same format to deduplicate and combine with the base URL correctly in the next step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 8 — Deduplicate and limit&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add a Remove Duplicates node (deduplicate on links field) followed by a Limit node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`Max items: 3
Keep:      First Items`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three pages per prospect is the sweet spot. Enough to find specific details, not enough to run up excessive API costs or hit rate limits on the target site.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 9 — Fetch internal pages&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add another HTTP Request node to fetch each filtered internal URL:&lt;/p&gt;

&lt;p&gt;`&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Method: GET&lt;br&gt;
URL:    {{ $json.website_url }}{{ $json.links }}&lt;br&gt;
Error handling: Continue on error&lt;br&gt;
`&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The URL concatenates the base domain from the original lead data with the relative path extracted from the link list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 10 — HTML to Markdown&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add an HTML to Markdown node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`Mode:            HTML to Markdown
HTML:            {{ $json.data ? $json.data : "&amp;lt;div&amp;gt;empty&amp;lt;/div&amp;gt;" }}
Destination Key: data`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Markdown is significantly more token-efficient than HTML for AI processing. Stripping HTML tags reduces the content you are passing to the AI model by 60–80%, which directly reduces API cost and improves the quality of the AI's analysis by removing noise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 11 — AI page summariser&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add an AI Agent node with this system prompt:&lt;/p&gt;

&lt;p&gt;You are a helpful, intelligent website scraping assistant.&lt;/p&gt;

&lt;p&gt;You are provided a Markdown scrape of a website page. &lt;br&gt;
Your task is to provide a two-paragraph abstract of what this page is about.&lt;/p&gt;

&lt;p&gt;Return in this JSON format:&lt;br&gt;
{"abstract":"your abstract goes here"}&lt;/p&gt;

&lt;p&gt;Rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your extract should be comprehensive — similar level of detail 
as an abstract to a published paper.&lt;/li&gt;
&lt;li&gt;Use a straightforward, factual writing style.&lt;/li&gt;
&lt;li&gt;Focus on what is unique or distinctive about this business or page.&lt;/li&gt;
&lt;li&gt;Note any specific features, products, services, or differentiators 
that would be useful for personalised outreach.&lt;/li&gt;
&lt;li&gt;Return ONLY the JSON object. No backticks. No explanation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Model: GPT-4 mini via OpenRouter — approximately $0.0003 per page abstract.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 12 — Merge all abstracts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After the loop processes all three pages, merge the abstract outputs into a single node. Use a Merge node set to "Combine All Items."&lt;br&gt;
Pass the merged abstracts to the final AI Agent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 13 — Icebreaker generator&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add a final AI Agent node with this system prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`You are an expert cold email copywriter specializing in personalized outreach.

You will receive multiple website page summaries for a prospect company.
Your task is to write a multi-line, personalized icebreaker for a cold email.

Rules:
- Reference 1-2 SPECIFIC details you found on their website
  (features, initiatives, content, language they use about themselves)
- Sound like you actually explored the website — not like you read a summary
- Be conversational, warm, and curious — not salesy
- Keep it to 2-3 sentences maximum
- Address the first_name directly at the start
- End with a natural transition into your pitch ("Wanted to run something by you...")

First name: {{ $('Loop Over Items').first().json.first_name }}
Website summaries: [all abstract outputs concatenated here]

Return ONLY the icebreaker text. No JSON. No explanation.
`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 14 — Write back to Google Sheets&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add a Google Sheets node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`Operation:    Update Row
Match on:     email = {{ $json.email }}
Update:
  icebreaker: {{ $json.icebreaker }}`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The icebreaker column fills in for each row as the workflow processes it. When the run is complete, your Sheet has a personalised icebreaker for every prospect with a valid website URL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What breaks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;HTTP Request returns 403 on most sites: The site is blocking the default n8n user agent. Add a custom header: User-Agent: Mozilla/5.0 (compatible; outreach-research/1.0). This passes most basic anti-scraping checks.&lt;br&gt;
AI Agent returns a JSON error instead of abstract: The page content was empty (HTTP request returned an error page). The {{ $json.data ? $json.data : "&lt;/p&gt;empty" }} expression in the HTML to Markdown node handles this — it passes a dummy div instead of null, which prevents the AI from throwing on empty input.

&lt;p&gt;Loop produces mixed data across prospects: You are not using batch size 1. Set Loop Over Items batch size to exactly 1.&lt;br&gt;
Icebreakers sound generic despite the workflow running: The AI is receiving summaries but not finding distinctive details. Check your Limit node — if it is set to 0 or is missing, you may only be scraping the homepage. Set it to 3 to ensure About and Services pages are included.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Running cost&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;OpenRouter GPT-4 mini: approximately $0.002 per prospect (3 page abstracts + 1 icebreaker generation).&lt;br&gt;
100 prospects = $0.20 in API costs.&lt;br&gt;
1,000 prospects = $2.00.&lt;/p&gt;

&lt;p&gt;Compare this to $5–$50 per manually researched and written personalised opener. The cost case is immediate.&lt;br&gt;
Workflow JSON at elevoras.com.&lt;br&gt;
What niche are you targeting with cold outreach? Drop it in the comments — happy to suggest prompt adjustments for specific industries.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Build an AI Newsletter Agent That Reads the Web Every Morning (n8n + OpenRouter + Gemini)</title>
      <dc:creator>Jayanth</dc:creator>
      <pubDate>Sun, 19 Apr 2026 17:44:56 +0000</pubDate>
      <link>https://forem.com/grewup/build-an-ai-newsletter-agent-that-reads-the-web-every-morning-n8n-openrouter-gemini-3nbp</link>
      <guid>https://forem.com/grewup/build-an-ai-newsletter-agent-that-reads-the-web-every-morning-n8n-openrouter-gemini-3nbp</guid>
      <description>&lt;p&gt;&lt;strong&gt;What this builds&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An automated AI newsletter agent that runs every night at midnight, searches the web for the most relevant news in your chosen niche, formats it into a professional HTML newsletter, emails it to you automatically, and logs every story to Google Sheets to prevent duplicates in future runs.&lt;/p&gt;

&lt;p&gt;You wake up to fresh, relevant, personalised industry news. No manual research. No subscriptions to dozens of sources. No duplicate stories.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Schedule Trigger (midnight daily)
        ↓
AI Agent (OpenRouter — GPT-4 via OpenRouter)
  Tools attached:
  ├── Google Gemini (live web search — past week)
  └── Think tool (internal quality check)
  Output: structured JSON with subject + 3 news items
        ↓
Gmail — send formatted HTML newsletter
        ↓
Code node — unbundle 3 items into individual rows
        ↓
Google Sheets — log each story (duplicate prevention)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;## Step 1 — Schedule Trigger&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add a Schedule Trigger node. Set it to fire at midnight daily.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;Trigger&lt;/span&gt; &lt;span class="py"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Days&lt;/span&gt;
&lt;span class="err"&gt;Days&lt;/span&gt; &lt;span class="py"&gt;between&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="py"&gt;Hour&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;             &lt;span class="s"&gt;0 (midnight)&lt;/span&gt;
&lt;span class="py"&gt;Minute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;           &lt;span class="s"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;## Step 2 — AI Agent node&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add an AI Agent node. Rename it &lt;strong&gt;News Research Agent&lt;/strong&gt;.&lt;br&gt;
Connect OpenRouter Chat Model. Toggle "Require Specific Output Format" to ON.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Send me the newsletter for today.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;System prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;You&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;an&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;expert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;newsletter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;curator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;specializing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;AI&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;tool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;launches&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="err"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;industry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;developments.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;mission&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;research&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;internet&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="err"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;daily&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;newsletter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;summarizing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;most&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;impactful&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;AI&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="err"&gt;tool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;launches&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;developments&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;past&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;week.&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;ROLE:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;AI&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Industry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Newsletter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Curator&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;OUTPUT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;COUNT:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Exactly&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;news&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;items&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;CHARACTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;LIMIT:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;words&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;per&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;news&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;maximum&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;**GUIDELINES:**&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Focus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;NEW&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;tool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;launches,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;significant&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;updates,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;industry-changing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;developments&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Prioritize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;tools&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;that&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;solve&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;real&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;business&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;problems&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;why&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;each&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;development&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;matters&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;professionals&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Verify&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;information&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;multiple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;sources&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;when&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;possible&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Maintain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;an&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;engaging,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;professional&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;tone&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;STRUCTURE:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Provide&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;response&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;following&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;JSON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;format:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subject_line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AI Weekly: [Key Theme] - [Date]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"news_items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Tool/Development Name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Brief description and key features"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"why_it_matters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Impact explanation for professionals"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Primary source URL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Tool Launch/Update/Industry News"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;TOOLS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;AVAILABLE:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;You&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;have&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;access&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;internet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;search,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;thinking&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="err"&gt;capabilities,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Google&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Sheets&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;avoid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;duplicate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;content.&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;AVOID:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Outdated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;news&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(older&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;than&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;week)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Overly&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;technical&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;jargon&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;without&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;explanation&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Duplicate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;previous&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;newsletters&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Speculation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;unverified&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;rumors&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3 — Add the Gemini search tool&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Click the Tools tab inside the AI Agent node. Add Google Gemini as a tool.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;          &lt;span class="s"&gt;gemini-1.5-flash&lt;/span&gt;
&lt;span class="err"&gt;Search&lt;/span&gt; &lt;span class="py"&gt;recency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Past week&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4 — Add the Think tool&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Click Tools again. Add the Think tool. Paste this description:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use the tool to think about something. It will not obtain new information 
or change the database, but just append the thought to the log. Use it 
when complex reasoning or some cache memory is needed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 5 — Structured Output Parser&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Configure with this JSON schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subject_line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AI Weekly Update - March 15, 2024"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"news_items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Revolutionary AI Tool Launch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Description of the tool and its capabilities."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"why_it_matters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Impact on industry professionals."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Tool Launch"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Select Google Gemini Chat Model as the model for the parser. Model: gemini-1.5-flash.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 6 — Gmail node&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;Operation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;Send Message&lt;/span&gt;
&lt;span class="py"&gt;To&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;         &lt;span class="s"&gt;your@email.com&lt;/span&gt;
&lt;span class="py"&gt;Subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;{{ $json.output.subject_line }}&lt;/span&gt;
&lt;span class="err"&gt;Email&lt;/span&gt; &lt;span class="py"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HTML&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Message body (Expression mode):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 10px;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    {{ $json.output.subject_line }}
  &lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

  {{ $json.output.news_items.map(item =&amp;gt; `
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"margin: 25px 0; padding: 20px; background: #f8f9fa; border-radius: 8px; border-left: 4px solid #3498db;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"color: #2c3e50; margin-top: 0;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;${item.title}&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"background: #3498db; color: white; padding: 3px 10px; border-radius: 12px; font-size: 12px;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;${item.category}&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"color: #444; line-height: 1.6; margin-top: 12px;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;${item.content}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"background: #e8f4fd; padding: 12px; border-radius: 6px; margin-top: 12px;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;strong&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"color: #2980b9;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Why it matters:&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"color: #444; margin: 5px 0 0 0;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;${item.why_it_matters}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"${item.source}"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"color: #3498db; text-decoration: none; font-size: 13px;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Read source →&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  `).join('') }}

  &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"color: #888; font-size: 12px; text-align: center; margin-top: 30px;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Generated by your AI newsletter agent · {{ new Date().toLocaleDateString() }}
  &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 7 — Code node (unbundle)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;News Research Agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subjectLine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subject_line&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newsList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;news_items&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;news&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;newsList&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;subject_line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;subjectLine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;news&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;news&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;news&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;news&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;date_logged&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;T&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 8 — Google Sheets logging&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;Operation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;Append Row&lt;/span&gt;
&lt;span class="py"&gt;Columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="py"&gt;subject_line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;{{ $json.subject_line }}&lt;/span&gt;
  &lt;span class="py"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;        &lt;span class="s"&gt;{{ $json.title }}&lt;/span&gt;
  &lt;span class="py"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="s"&gt;{{ $json.content }}&lt;/span&gt;
  &lt;span class="py"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="s"&gt;{{ $json.source }}&lt;/span&gt;
  &lt;span class="py"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;{{ $json.category }}&lt;/span&gt;
  &lt;span class="py"&gt;date_logged&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;{{ $json.date_logged }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What breaks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent fabricates news instead of searching:&lt;/strong&gt; Gemini tool is not connected. Check the Tools tab — Gemini should show a green connection indicator.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Structured Output Parser fails:&lt;/strong&gt; Add this to your system prompt: "Return ONLY valid JSON. No markdown, no backticks, no explanation before or after the JSON object."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code node throws undefined error:&lt;/strong&gt; The node name in $('News Research Agent') does not match your actual node name. Case sensitive, space sensitive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gmail subject shows raw expression:&lt;/strong&gt; Switch from Fixed to Expression mode in the subject field.&lt;/p&gt;

&lt;p&gt;Workflow JSON available at elevoras.com. Import into n8n, connect your credentials, and activate.&lt;/p&gt;

&lt;p&gt;What niche are you building this for? Drop it in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>opensource</category>
    </item>
    <item>
      <title>N8N Workflow — Product Photo to Marketing Video, Automatically (Runway ML + OpenRouter)</title>
      <dc:creator>Jayanth</dc:creator>
      <pubDate>Sat, 18 Apr 2026 07:44:06 +0000</pubDate>
      <link>https://forem.com/grewup/n8n-workflow-product-photo-to-marketing-video-automatically-runway-ml-openrouter-55ib</link>
      <guid>https://forem.com/grewup/n8n-workflow-product-photo-to-marketing-video-automatically-runway-ml-openrouter-55ib</guid>
      <description>&lt;p&gt;&lt;strong&gt;What this builds&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A fully automated product marketing pipeline. You submit a product photo, title, and description through a form. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The workflow:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;`1. Uploads the original image to Google Drive for storage&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sends product details to an AI agent (OpenRouter) which generates a professional photography prompt&lt;/li&gt;
&lt;li&gt;Downloads the image as binary from Google Drive&lt;/li&gt;
&lt;li&gt;Uploads it to ImageBB to create a public URL&lt;/li&gt;
&lt;li&gt;Sends the public image URL + a video generation prompt to Runway ML gen-4-turbo&lt;/li&gt;
&lt;li&gt;Polls the render status until the video is complete&lt;/li&gt;
&lt;li&gt;Emails the customer the finished image URL and video link automatically`&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Input:&lt;/strong&gt; a basic product photo + title + description.&lt;/p&gt;

&lt;p&gt;**Output: **a professional marketing video delivered to their inbox in under 15 minutes.&lt;/p&gt;

&lt;p&gt;**Cost per video: **roughly $0.25 (Runway ML charge) plus minimal OpenRouter usage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`n8n Form Trigger (photo + title + description + email)
        ↓
Google Drive — upload original image (binary)
        ↓
AI Agent (OpenRouter) — generate professional photography prompt
        ↓
Google Drive — download image as binary
        ↓
HTTP Request → ImageBB API — upload image, get public URL
        ↓
HTTP Request → Runway ML API — image_to_video (gen-4-turbo)
        ↓
Wait 60s → HTTP GET → check task status → IF running → wait 5s → loop
        ↓ [loop until SUCCEEDED]
Gmail — send email with image URL + video URL`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 1 — Form Trigger&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add an n8n Form Trigger node. Configure four fields:&lt;br&gt;
Field 1 — Product Photo&lt;br&gt;
  Type:      File upload&lt;br&gt;
  Required:  Yes&lt;br&gt;
  Multiple:  No&lt;br&gt;
  Name:      product photo&lt;/p&gt;

&lt;p&gt;Field 2 — Product Title&lt;br&gt;
  Type:      Text&lt;br&gt;
  Required:  Yes&lt;br&gt;
  Placeholder: your product name&lt;br&gt;
  Name:      product title&lt;/p&gt;

&lt;p&gt;Field 3 — Product Description&lt;br&gt;
  Type:      Text&lt;br&gt;
  Required:  Yes&lt;br&gt;
  Name:      product description&lt;/p&gt;

&lt;p&gt;Field 4 — Email Address&lt;br&gt;
  Type:      Email&lt;br&gt;
  Required:  Yes&lt;br&gt;
  Placeholder: &lt;a href="mailto:name@example.com"&gt;name@example.com&lt;/a&gt;&lt;br&gt;
  Name:      email&lt;/p&gt;

&lt;p&gt;Form title: "Go to Market" — this becomes a client-facing intake form if you share the Production URL.&lt;br&gt;
Click Execute Node to generate your form. Pin the test data after submitting once — this saves you re-filling the form at every subsequent node test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — Upload to Google Drive&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add a Google Drive node.&lt;br&gt;
Operation:   Upload File&lt;br&gt;
File:        {{ $('Form Trigger').first().binary['product photo'] }}&lt;br&gt;
File name:   {{ $('Form Trigger').first().json['product title'] }} (original)&lt;br&gt;
Folder:      product creatives&lt;br&gt;
Create the "product creatives" folder in your Google Drive before connecting. The dynamic file naming means each upload is labelled with the product name so your Drive does not become a folder of unnamed files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — AI Agent (generate the photography prompt)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add an AI Agent node. Connect OpenRouter credentials.&lt;br&gt;
The raw product description from a form submission does not make a good image generation prompt. This node transforms it into professional photography language that Runway ML can use.&lt;br&gt;
System prompt:&lt;br&gt;
You are a world-class marketing strategist and expert text-to-image prompt engineer &lt;br&gt;
specializing in hyper-realistic product photography prompts for AI image generation.&lt;/p&gt;

&lt;p&gt;When given a product description, craft a detailed professional prompt that produces:&lt;/p&gt;

&lt;p&gt;`- Hyper-realistic studio photography&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clean minimalistic aesthetic&lt;/li&gt;
&lt;li&gt;Product as main subject with sharp detail and perfect lighting&lt;/li&gt;
&lt;li&gt;Complementary background (soft gradient, light-colored, or pure white)&lt;/li&gt;
&lt;li&gt;Professional lighting: softbox, studio lights, or natural soft shadows&lt;/li&gt;
&lt;li&gt;High-end premium feel`&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Output Format:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Write a single text-to-image prompt ready for direct input into an AI model.&lt;br&gt;
Be direct and descriptive. No unnecessary repetition.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User message:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Product: {{ $('Form Trigger').first().json['product title'] }}
Description: {{ $('Form Trigger').first().json['product description'] }}
The agent output is the photography prompt string. Save it — you do not actually send it to an image generator in this workflow (Runway handles that). You will use it as context for the video generation prompt in Step 6.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4 — Download from Google Drive (as binary)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add a second Google Drive node.&lt;/p&gt;

&lt;p&gt;Operation:   Download File&lt;br&gt;
By:          ID&lt;br&gt;
File ID:     {{ $('Upload to Google Drive').first().json.id }}&lt;br&gt;
This converts the stored file back into binary data that can be processed by external APIs. You cannot send a Google Drive link to ImageBB — you need the actual binary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5 — Upload to ImageBB (create public URL)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add an HTTP Request node.&lt;br&gt;
Method:      POST&lt;br&gt;
URL:         &lt;a href="https://api.imgbb.com/1/upload?key=%5BYOUR_IMGBB_API_KEY%5D" rel="noopener noreferrer"&gt;https://api.imgbb.com/1/upload?key=[YOUR_IMGBB_API_KEY]&lt;/a&gt;&lt;br&gt;
Body type:   Form Data&lt;br&gt;
Fields:&lt;br&gt;
  image:     [binary data from previous Google Drive node]&lt;br&gt;
Get your free API key from imgbb.com → your account dashboard → API.&lt;br&gt;
ImageBB returns a JSON response with multiple URL formats. Use data.url — the direct image URL without any HTML wrapping. This is what Runway ML requires.&lt;br&gt;
javascript// The field you need from the response:&lt;br&gt;
{{ $json.data.url }}&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 6 — Generate video with Runway ML&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add an HTTP Request node.&lt;br&gt;
Method:  POST&lt;br&gt;
URL:     &lt;a href="https://api.dev.runwayml.com/v1/image_to_video" rel="noopener noreferrer"&gt;https://api.dev.runwayml.com/v1/image_to_video&lt;/a&gt;&lt;br&gt;
Headers:&lt;br&gt;
  Authorization:    Bearer [YOUR_RUNWAY_API_KEY]&lt;br&gt;
  x-runway-version: 2024-11-06&lt;br&gt;
  Content-Type:     application/json&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Body (JSON):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gen4_turbo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"promptImage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{ $('ImageBB Upload').first().json.data.url }}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"promptText"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Create a highly professional marketing video from the provided product photo. Simulate a smooth, realistic product rotation — slowly turning in place. Movement should be continuous, slow, and elegant — no sudden pans, jerks, or cuts. Always keep the entire product fully in frame, centered, and clearly visible. Avoid zooming in or cropping. Focus on a premium, clean, modern aesthetic suitable for commercial marketing. No flashy effects, transitions, or overlays."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ratio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"960:960"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Duration note: 10 seconds costs approximately $0.50 in Runway ML credits. 5 seconds costs approximately $0.25. For product demos, 10 seconds gives the rotation enough time to feel complete. For social media ads where shorter is better, use 5 seconds.&lt;br&gt;
Ratio note: 960:960 (square) works across all platforms. If your client specifically needs 9:16 vertical for Reels/Shorts, change to "ratio": "720:1280".&lt;br&gt;
The API returns a task_id immediately — the video is queued, not rendered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 7 — Polling loop (wait for render)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Video generation with Runway ML takes 60–180 seconds. Build a polling loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add a Wait node:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Wait: 60 seconds&lt;br&gt;
Add an HTTP Request node to check status:&lt;br&gt;
Method:  GET&lt;br&gt;
URL:     &lt;a href="https://api.dev.runwayml.com/v1/tasks/%7B%7B" rel="noopener noreferrer"&gt;https://api.dev.runwayml.com/v1/tasks/{{&lt;/a&gt; $('Runway Generate').first().json.id }}&lt;br&gt;
Headers:&lt;br&gt;
  Authorization:    Bearer [YOUR_RUNWAY_API_KEY]&lt;br&gt;
  x-runway-version: 2024-11-06&lt;br&gt;
Add an IF node:&lt;br&gt;
Condition: {{ $json.status }} equals "RUNNING"&lt;br&gt;
  True  → Wait 5 seconds → loop back to status check&lt;br&gt;
  False → continue to Gmail&lt;/p&gt;

&lt;p&gt;When status equals "SUCCEEDED", the response includes an output array. The first element — output[0] — is the direct video URL.&lt;br&gt;
If status equals "FAILED", add an error branch that sends an email notifying the client of the failure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 8 — Send the email&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add a Gmail node. Connect via OAuth in n8n credentials.&lt;br&gt;
To:      {{ $('Form Trigger').first().json['email'] }}&lt;br&gt;
Subject: Marketing Materials: {{ $('Form Trigger').first().json['product title'] }}&lt;br&gt;
Type:    HTML&lt;br&gt;
Body:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;`html&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Hey,&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Your marketing materials for &lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;{{ $('Form Trigger').first().json['product title'] }}&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt; are ready.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;&lt;/span&gt;Enhanced product image:&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{{ $('ImageBB Upload').first().json.data.url }}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;View image →&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;&lt;/span&gt;Marketing video:&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{{ $('Runway Status Check').first().json.output[0] }}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;View video →&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Both files are ready to download and use in your ads immediately.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Cheers,&lt;span class="nt"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;
[Your name]&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What breaks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Runway ML returns 401: Your API key is wrong or your x-runway-version header is outdated. Check the current version string in Runway's API documentation — it updates periodically.&lt;br&gt;
ImageBB returns 400: The image file is too large (ImageBB free tier has a 32MB limit) or the binary data is not being passed correctly. Verify the Google Drive download node is outputting binary data, not a file reference.&lt;/p&gt;

&lt;p&gt;Status check loop never exits: Add a counter variable and a maximum iteration count (15 attempts). If the loop runs 15 times without a "SUCCEEDED" status, break to an error email. Runway rarely takes longer than 3 minutes — if it does, the task has likely failed silently.&lt;br&gt;
Gmail authentication fails: Re-authorise the Gmail OAuth credential in n8n. Google OAuth tokens expire and need periodic refresh.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Extending this workflow&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For agencies serving multiple clients, replace the Form Trigger with a webhook and build a simple intake page that clients access directly. Their submission triggers the workflow and they receive the video by email without any manual step from you.&lt;br&gt;
For ecommerce stores with product catalogues, connect a Google Sheets trigger that iterates through product rows and generates a video for each SKU in batch mode.&lt;/p&gt;

&lt;p&gt;The workflow JSON is at elevoras.com. Import directly into n8n and add your API keys.&lt;/p&gt;

&lt;p&gt;What are you using this for? Drop your use case in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>N8N on WebspaceKit — Unlimited Automations for $6/Month, Zero Terminal Required</title>
      <dc:creator>Jayanth</dc:creator>
      <pubDate>Fri, 17 Apr 2026 17:26:57 +0000</pubDate>
      <link>https://forem.com/grewup/n8n-on-webspacekit-unlimited-automations-for-6month-zero-terminal-required-4k7l</link>
      <guid>https://forem.com/grewup/n8n-on-webspacekit-unlimited-automations-for-6month-zero-terminal-required-4k7l</guid>
      <description>&lt;p&gt;&lt;strong&gt;ARTICLE BODY:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this solves&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;n8n Cloud's Starter plan costs $20/month and caps your workflow executions. Build serious automations — daily triggers, webhook listeners, AI API calls — and you hit that cap within days.&lt;br&gt;
Self-hosting removes the cap. But the usual path requires Docker setup, SSL configuration, reverse proxy management, and ongoing server administration. Most people look at that list and go back to the cloud plan.&lt;br&gt;
WebspaceKit's managed n8n hosting is the third path: you get self-hosted pricing and unlimited executions, with zero server administration. The entire setup runs in under five minutes. No command line. No Docker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1 — Navigate to the n8n hosting page&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Go to webspacekit.com/n8n-cloud-hosting/ and scroll down to the pricing section. Click "Choose Plan."&lt;br&gt;
You will see billing period options: monthly, 12 months, and 24 months. The longer the commitment, the lower the monthly rate. For testing: 12 months. For production business automations you are already committed to: 24 months for the lowest per-month rate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — Create your account and complete payment&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Fill in your name, email, and billing details. This is your WebspaceKit account — different from the n8n login you will create in Step 5.&lt;br&gt;
Once payment completes, you are taken to your client area dashboard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — Find your n8n service&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the client area, your active services appear in a list. Click on your n8n hosting service. Look for the "Go to Setup" button and click it.&lt;br&gt;
This opens the n8n initialisation page — a simple form with three fields.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4 — Configure your n8n instance&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Three fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;`Owner First Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;your first name&lt;/span&gt;
&lt;span class="na"&gt;Owner Email&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;       &lt;span class="s"&gt;your n8n login email&lt;/span&gt;
&lt;span class="na"&gt;Owner Password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;strong password for your n8n dashboard`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Important: the Owner Email and Password here are your n8n login credentials — separate from your WebspaceKit account credentials. Use different passwords for each.&lt;br&gt;
Click "Next" or "Complete Setup."&lt;/p&gt;

&lt;p&gt;WebspaceKit provisions your instance. This takes 30–60 seconds. When the success screen appears, you will see your n8n dashboard URL:&lt;br&gt;
&lt;a href="https://your-instance.webspacekit.com" rel="noopener noreferrer"&gt;https://your-instance.webspacekit.com&lt;/a&gt;&lt;br&gt;
Click it. Your n8n instance opens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5 — First login and orientation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Log in with the email and password you set in Step 4.&lt;br&gt;
Your dashboard loads with whatever starting template came with your plan. If you are on a plan that includes pre-built workflow templates, you will see those immediately in your workflows list.&lt;/p&gt;

&lt;p&gt;The main panels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`Workflows     — your automation canvas list
Credentials   — OAuth connections to your apps
Executions    — logs of every workflow run
Settings      — n8n configuration and user management`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 6 — Connect your first app&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every app n8n connects to requires a credential. For Gmail:&lt;/p&gt;

&lt;p&gt;Settings → Credentials → New Credential → search "Google"&lt;br&gt;
Select "Google OAuth2 API"&lt;br&gt;
Follow the Google authorisation flow&lt;br&gt;
Save the credential&lt;/p&gt;

&lt;p&gt;That credential is now available to every Gmail node in every workflow you build. You authenticate once per service, not once per workflow.&lt;br&gt;
Common credential types to set up immediately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`Google (OAuth) — Gmail, Google Sheets, Google Drive
Notion (API key) — databases, pages
Slack (OAuth) — messages, channels
HTTP Request (no auth) — public APIs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 7 — Build your first workflow&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Click "New Workflow" → drag a Schedule Trigger onto the canvas.&lt;br&gt;
A minimal daily briefing workflow to start:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
&lt;/code&gt;Node 1: Schedule Trigger&lt;br&gt;
  Mode:     Days&lt;br&gt;
  Hour:     8&lt;br&gt;
  Minute:   0&lt;/p&gt;

&lt;p&gt;Node 2: HTTP Request&lt;br&gt;
  Method:   GET&lt;br&gt;
  URL:      &lt;a href="https://hacker-news.firebaseio.com/v0/topstories.json?limitToFirst=5&amp;amp;orderBy=%22$key" rel="noopener noreferrer"&gt;https://hacker-news.firebaseio.com/v0/topstories.json?limitToFirst=5&amp;amp;orderBy="$key&lt;/a&gt;"&lt;/p&gt;

&lt;p&gt;Node 3: Gmail — Send Message&lt;br&gt;
  To:       &lt;a href="mailto:your@email.com"&gt;your@email.com&lt;/a&gt;&lt;br&gt;
  Subject:  Morning Briefing — {{ $now.toLocaleDateString() }}&lt;br&gt;
  Body:     {{ JSON.stringify($json, null, 2) }}&lt;code&gt;&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;plaintext&lt;/p&gt;

&lt;p&gt;Wire them in sequence. Click "Execute Workflow" to test. Check your inbox.&lt;/p&gt;

&lt;p&gt;That is the pattern. Every workflow in n8n follows the same structure: trigger → action(s) → optional logic nodes.&lt;br&gt;
The workflows that actually save the most time&lt;/p&gt;

&lt;p&gt;Three high-ROI starting points based on usage patterns:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
&lt;/code&gt;Lead capture from form submissions → CRM:&lt;br&gt;
Webhook Trigger → HTTP Request (validate data) → &lt;br&gt;
Notion (create page in leads database) → &lt;br&gt;
Gmail (send confirmation email)&lt;br&gt;
Daily social content scheduler:&lt;br&gt;
Schedule Trigger (9 AM) → &lt;br&gt;
Google Sheets (read next queued post) → &lt;br&gt;
HTTP Request (post to Twitter/X API) → &lt;br&gt;
Google Sheets (mark row as posted)&lt;br&gt;
Automatic invoice processing:&lt;br&gt;
Gmail Trigger (attachment received) → &lt;br&gt;
Extract attachments → &lt;br&gt;
Google Drive (save to invoices folder) → &lt;br&gt;
Notion (log invoice details)&lt;br&gt;
Each of these replaces 15–30 minutes of daily manual work. At three workflows running daily, you recover roughly an hour per day within the first week.&lt;br&gt;
&lt;code&gt;&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;What tends to break on first setup&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Google OAuth redirect error: add your WebspaceKit n8n instance URL to the list of authorised redirect URIs in your Google Cloud Console OAuth app settings. Takes two minutes.&lt;/p&gt;

&lt;p&gt;Workflow triggers but produces no visible output: check the Executions tab. Every execution is logged with full node-by-node data showing exactly what passed between nodes and where execution stopped.&lt;br&gt;
Schedule Trigger not firing: confirm the workflow is Active (toggle in top right of the workflow editor, turns blue when active). A workflow in Test mode only fires when you manually trigger it from the editor.&lt;br&gt;
Webhook URL not receiving data: WebspaceKit assigns you a fixed subdomain. Confirm the webhook URL in your n8n Form Trigger matches what you have registered in the external service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scaling up&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once the basic setup is running cleanly, the natural next steps:&lt;br&gt;
Add error handling to critical workflows. An Error Trigger node catches any workflow failure and sends you a notification. For workflows that touch client data or financial systems, this is not optional.&lt;br&gt;
Error Trigger → Gmail (send failure alert with error details)&lt;br&gt;
Build a workflow template library. Export your best workflows as JSON (top menu → Download) and keep them in a folder. When you build for a new use case, start from the closest matching template rather than from scratch.&lt;/p&gt;

&lt;p&gt;Connect n8n to your AI workflows. The HTTP Request node handles any API call — including OpenAI, Anthropic, or any LLM provider. This is how you build the kind of AI-powered automations covered elsewhere on this blog (news-to-WordPress, Instagram comment response, video generation pipelines) without needing a separate orchestration layer.&lt;/p&gt;

&lt;p&gt;Full setup guide with every screenshot at elevoras.com.&lt;br&gt;
Automating something interesting on WebspaceKit or running into a node configuration issue? Drop it in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>nocode</category>
      <category>productivity</category>
    </item>
    <item>
      <title>N8N Workflow That Auto-Creates Viral YouTube Shorts While You Sleep</title>
      <dc:creator>Jayanth</dc:creator>
      <pubDate>Wed, 15 Apr 2026 14:07:07 +0000</pubDate>
      <link>https://forem.com/grewup/n8n-workflow-that-auto-creates-viral-youtube-shorts-while-you-sleep-2moe</link>
      <guid>https://forem.com/grewup/n8n-workflow-that-auto-creates-viral-youtube-shorts-while-you-sleep-2moe</guid>
      <description>&lt;p&gt;&lt;strong&gt;What this builds&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A fully automated faceless YouTube Shorts pipeline. You add a topic. The workflow:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Reads your topic from Google Sheets&lt;br&gt;
Generates a short-form script optimised for vertical video (GPT-4 via OpenRouter)&lt;br&gt;
Creates AI image prompts for each scene&lt;br&gt;
Sends structured content to JSON2Video for vertical video rendering&lt;br&gt;
Polls render status until complete&lt;br&gt;
Downloads the MP4&lt;br&gt;
Uploads directly to YouTube as a Short&lt;br&gt;
Updates your Sheet with the video URL&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Target output: 60-second vertical Shorts, fully faceless, 9:16 format, rendered and uploaded without any manual steps.&lt;br&gt;
Architecture&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Schedule Trigger (daily or on-demand)
        ↓
Google Sheets — read first "to-do" row
        ↓
Script Agent — GPT-4 generates hook + body + CTA + scene image prompts
        ↓
JSON2Video API — POST with vertical template → returns project_id
        ↓
Wait 90s → GET render status → Switch (done / running / error)
        ↓ [loop until done]
HTTP Request — download MP4 as binary file
        ↓
YouTube node — upload as Short (9:16, under 60s)
        ↓
Google Sheets — update status + video URL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 1 — Google Sheet setup&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create a new Google Sheet with these exact column headers:&lt;br&gt;
Topic | Script Status | Upload Status | Video URL&lt;br&gt;
Add topics. Set Script Status to to-do for each. Examples:&lt;br&gt;
5 AI tools that save 10 hours a week   | to-do | to-do |&lt;br&gt;
Why your HeyGen videos look robotic    | to-do | to-do |&lt;br&gt;
GoHighLevel vs HubSpot — who wins      | to-do | to-do |&lt;br&gt;
The workflow processes one row per run — only rows where Script Status equals to-do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — Google Sheets node&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add a Google Sheets node as your trigger.&lt;br&gt;
Operation: Get Rows&lt;br&gt;
Filter:    Script Status = "to-do"&lt;br&gt;
Limit:     1 (first matching row only)&lt;br&gt;
Critical: toggle "Return only first matching row" ON. Without this, the workflow attempts to process your entire backlog in one run — GPU costs spiral and the workflow times out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — Script Agent (GPT-4 via OpenRouter)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the node that determines whether your Short will perform or get skipped. The script structure for Shorts is different from long-form — hooks must be under 3 seconds of spoken word, and every scene needs to visually match the spoken content.&lt;br&gt;
Add an AI Agent node. Connect OpenRouter credentials.&lt;br&gt;
System prompt:&lt;br&gt;
You are an expert YouTube Shorts scriptwriter.&lt;br&gt;
You write addictive, fast-paced scripts that hook viewers in under 3 seconds.&lt;br&gt;
Always structure scripts as: Hook → Problem → Solution → Proof → CTA&lt;br&gt;
Keep total script under 60 seconds of spoken content.&lt;br&gt;
Return ONLY clean JSON, no backticks, no explanation.&lt;br&gt;
User message:&lt;br&gt;
Write a YouTube Shorts script about: "{{ $json.Topic }}"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Return this exact JSON structure:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hook"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"First spoken line — must create immediate curiosity or shock. Under 8 words."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scenes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"spoken"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Spoken content for this scene (2-3 sentences max)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"image_prompt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Visual scene description for AI image generation. Photorealistic, specific."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Final call to action line. Under 10 words."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YouTube title optimised for search. Under 60 characters."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hashtags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"hashtag1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hashtag2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hashtag3"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create exactly 4 scenes plus the hook. Total spoken content must be under 60 seconds.&lt;br&gt;
Model: openai/gpt-4o-mini via OpenRouter — approximately $0.001 per script.&lt;br&gt;
Enable "Require specific output format" — this enforces the JSON schema and prevents GPT from returning narrative text.&lt;br&gt;
Execute the node and verify: you should see a JSON object with a hook, scenes array of 4 objects, cta, title, and hashtags. If the schema breaks, add this to your prompt: "Double-check you have exactly 4 scene objects before returning."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4 — JSON2Video API (render the Short)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add an HTTP Request node.&lt;br&gt;
Method:  POST&lt;br&gt;
URL:     &lt;a href="https://api.json2video.com/v2/movies" rel="noopener noreferrer"&gt;https://api.json2video.com/v2/movies&lt;/a&gt;&lt;br&gt;
Headers:&lt;br&gt;
  x-api-key: [your JSON2Video API key]&lt;br&gt;
  Content-Type: application/json&lt;br&gt;
Critical — use a vertical template. JSON2Video has both landscape and vertical templates. For Shorts you must use a 9:16 vertical template. Create or duplicate one in your JSON2Video dashboard before this step. Note the template ID.&lt;br&gt;
Build your request body using expressions from the Script Agent output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;json&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"template"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[your_vertical_template_id]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"variables"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hook_text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{ $json.hook }}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scene1_spoken"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{ $json.scenes[0].spoken }}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scene1_image"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{ $json.scenes[0].image_prompt }}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scene2_spoken"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{ $json.scenes[1].spoken }}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scene2_image"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{ $json.scenes[1].image_prompt }}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scene3_spoken"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{ $json.scenes[2].spoken }}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scene3_image"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{ $json.scenes[2].image_prompt }}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scene4_spoken"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{ $json.scenes[3].spoken }}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scene4_image"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{ $json.scenes[3].image_prompt }}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"cta_text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{ $json.cta }}"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API returns a project_id immediately. Save this in a Set node for the polling loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5 — Status polling loop&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Shorts render faster than long-form videos — typically 2–5 minutes. The polling loop handles this.&lt;br&gt;
Node sequence:&lt;br&gt;
Wait node (90 seconds)&lt;br&gt;
        ↓&lt;br&gt;
HTTP Request GET → &lt;a href="https://api.json2video.com/v2/movies?project=%7B%7B" rel="noopener noreferrer"&gt;https://api.json2video.com/v2/movies?project={{&lt;/a&gt; $json.project_id }}&lt;br&gt;
        ↓&lt;br&gt;
Switch node&lt;br&gt;
  status = "done"       → output 1 → continue&lt;br&gt;
  status = "running"    → output 2 → Wait 15s → loop back&lt;br&gt;
  status = "preparing"  → output 2 → Wait 15s → loop back&lt;br&gt;&lt;br&gt;
  anything else         → output 3 → update Sheet "error" → stop&lt;br&gt;
Use a 90-second initial wait (shorter than long-form because Shorts render faster) and a 15-second loop interval.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 6 — Download the MP4&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Method:          GET&lt;br&gt;
URL:             {{ $json.video_url }}&lt;br&gt;
Response format: File   ← CRITICAL — must be set to File, not Auto-detect&lt;br&gt;
This is the same critical setting from the Instagram automation and the Top 10 Videos workflow. It is under Add Option → Response → Response Format. Without it you receive JSON metadata instead of binary video data. The YouTube upload will fail silently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 7 — YouTube upload&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add a YouTube node.&lt;br&gt;
Operation:    Upload Video&lt;br&gt;
Title:        {{ $('Script Agent').first().json.title }}&lt;br&gt;
Description:  {{ $('Script Agent').first().json.hashtags.join(' ') }}&lt;br&gt;
Category:     Science &amp;amp; Technology (or your niche)&lt;br&gt;
Privacy:      unlisted&lt;br&gt;
Binary data:  toggle ON&lt;br&gt;
YouTube Shorts detection: YouTube automatically identifies a video as a Short if it is vertical (9:16) and under 60 seconds. You do not need to add #Shorts manually — the format handles it. Adding #Shorts to the description is optional but can help discovery in the first 24 hours.&lt;br&gt;
Auth setup: You will need the YouTube Data API v3 enabled in Google Cloud Console. In your OAuth app settings, add your n8n redirect URI to the list of authorised redirect URIs. Connect the credential in n8n and authorise it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 8 — Update Google Sheet&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Operation:  Update Row&lt;br&gt;
Match on:   Topic = {{ $('Google Sheets').first().json.Topic }}&lt;br&gt;
Update:&lt;br&gt;
  Script Status  → "created"&lt;br&gt;
  Upload Status  → "unlisted"&lt;br&gt;
  Video URL      → &lt;a href="https://youtube.com/watch?v=%7B%7B" rel="noopener noreferrer"&gt;https://youtube.com/watch?v={{&lt;/a&gt; $json.id }}&lt;br&gt;
On the next workflow run, this row is skipped. Add new topics with Script Status to-do whenever you want more content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What breaks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;JSON2Video template mismatch: &lt;/p&gt;

&lt;p&gt;If your variable names in the POST body do not match the variable names in your JSON2Video template, the render succeeds but the output is empty scenes. Log your template variable names exactly and match them in the n8n expression.&lt;/p&gt;

&lt;p&gt;YouTube Short not recognised as a Short: Your template is not 9:16, or the video is over 60 seconds of content. Check your JSON2Video template dimensions and total scene duration.&lt;br&gt;
Script Agent returns wrong scene count: Add to prompt: "You MUST return exactly 4 scene objects in the scenes array. Count before returning." GPT-4 mini occasionally returns 3 or 5 without this constraint.&lt;br&gt;
Google Sheets filter stops working: Trailing space in the to-do cell value. Check with =LEN(A2) — if it returns more than 5, there is a hidden character.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scaling this up&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once the single-video flow runs cleanly:&lt;br&gt;
Replace the Schedule Trigger with a daily trigger set to 9 AM. Your Sheet becomes a content queue — add topics whenever inspiration hits, the workflow empties the queue daily.&lt;br&gt;
For batch processing multiple Shorts per day, change the Sheets Limit from 1 to 3. Run the workflow three times per day via a cron trigger. Watch your API costs — JSON2Video charges per render.&lt;/p&gt;

&lt;p&gt;Full article and workflow diagram at &lt;a href="https://elevoras.com/how-i-built-an-ai-system-with-n8n-that-creates-viral-shorts-while-i-sleep-and-you-can-too-in-10-minutes/" rel="noopener noreferrer"&gt;elevoras.com.&lt;/a&gt;&lt;br&gt;
Building a variation of this? Drop your topic niche and node count in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>n8nbrightdatachallenge</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
