{ "name": "RSS → Rendofy → Advanced Content Pipeline", "nodes": [ { "parameters": { "content": "## ADVANCED RSS → RENDOFY → MP4\n\n**Duplicate prevention:** state is written only after the MP4 downloads.\n**Filtering:** include/exclude keywords and summary length are configurable.\n**AI enrichment:** optional provider webhook creates a short script/summary.\n**Retry safety:** Rendofy submission is never auto-retried (no API idempotency key); download and publishing requests retry.\n**Publishing:** optional fan-out to your own platform adapters.\n\nThe Rendofy contract stays unchanged: `api_key`, `callback_url`, `payload`.", "width": 610, "height": 330 }, "id": "public-1", "name": "START HERE — Advanced pipeline", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ -1090, -490 ] }, { "parameters": { "content": "## REQUIRED\n\n- `RENDOFY_API_KEY`\n\n## OPTIONAL FILTERS\n\n- `CONTENT_INCLUDE_KEYWORDS` (comma-separated)\n- `CONTENT_EXCLUDE_KEYWORDS` (comma-separated)\n- `MIN_SUMMARY_LENGTH` (default 40)\n- `DUPLICATE_WINDOW_DAYS` (default 30)\n\n## OPTIONAL AI\n\n- `AI_ENRICHMENT_ENABLED=true`\n- `AI_SCRIPT_WEBHOOK_URL=https://...`\nReturn JSON: `{title?, summary?, script?}`.\n\n## OPTIONAL PUBLISHING\n\n- `PUBLISH_ENABLED=true`\n- `PUBLISH_TARGETS_JSON=[{\"name\":\"YouTube\",\"webhook_url\":\"https://...\"}]`\n\nPut provider credentials in your own adapters, never in this workflow.", "width": 630, "height": 420 }, "id": "public-2", "name": "CONFIGURATION — no secrets", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ -420, -500 ] }, { "parameters": { "content": "## VERIFIED CALLBACK HANDLING\n\nOn n8n 2.32.0 the Wait-node resume payload is a webhook envelope. **Normalize Rendofy Callback** uses `envelope.body` before reading `video_url`.\n\nA failed render returns retry-safe context. Do not blindly resubmit because the initial request may already have created a job.\n\nDownload and publishing use three attempts. Publishing adapters receive `job_id` as `idempotency_key` and should deduplicate it.", "width": 610, "height": 320 }, "id": "public-3", "name": "CALLBACK + RETRY SAFETY", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ 1180, -500 ] }, { "parameters": { "feedUrl": "https://example.com/feed.xml", "options": {} }, "id": "public-4", "name": "RSS Feed Trigger", "type": "n8n-nodes-base.rssFeedReadTrigger", "typeVersion": 1, "position": [ -1120, 70 ] }, { "parameters": { "mode": "runOnceForEachItem", "language": "javaScript", "jsCode": "const x=$input.item.json; const first=(...v)=>v.find(a=>typeof a==='string'&&a.trim())?.trim()||''; const clean=v=>String(v||'').replace(/<[^>]*>/g,' ').replace(/\\s+/g,' ').trim(); const url=first(x.link,x.guid,x.id); const title=first(x.title,'Untitled RSS item'); const summary=clean(first(x.contentSnippet,x.content,x.description,x.summary,title)); const key=Buffer.from(first(x.guid,x.id,url,title)).toString('base64url').slice(0,120); return {json:{title,summary,article_url:url,published_at:first(x.isoDate,x.pubDate),author:first(x.creator,x.author),source_name:url?new URL(url).hostname.replace(/^www\\./,''):'',dedupe_key:key}};" }, "id": "public-5", "name": "Normalize Feed Item", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -900, 70 ] }, { "parameters": { "mode": "runOnceForEachItem", "language": "javaScript", "jsCode": "const x=$input.item.json,e=$env,words=v=>String(v||'').split(',').map(s=>s.trim().toLowerCase()).filter(Boolean),cfg={include:words(e.CONTENT_INCLUDE_KEYWORDS),exclude:words(e.CONTENT_EXCLUDE_KEYWORDS),min:Number(e.MIN_SUMMARY_LENGTH||40),days:Number(e.DUPLICATE_WINDOW_DAYS||30),ai:String(e.AI_ENRICHMENT_ENABLED||'').toLowerCase()==='true',aiUrl:String(e.AI_SCRIPT_WEBHOOK_URL||''),publish:String(e.PUBLISH_ENABLED||'').toLowerCase()==='true',targets:String(e.PUBLISH_TARGETS_JSON||'[]')}; const state=$getWorkflowStaticData('global'),done=state.rendofyCompletedItems||{},cut=Date.now()-cfg.days*864e5; for(const[k,v]of Object.entries(done))if(vtext.includes(w)))why.push('no include keyword matched');if(cfg.exclude.some(w=>text.includes(w)))why.push('exclude keyword matched');if(cfg.ai&&!cfg.aiUrl)why.push('AI enabled but AI_SCRIPT_WEBHOOK_URL is missing');return {json:{...x,config:cfg,should_render:!why.length,skip_reasons:why}};" }, "id": "public-6", "name": "Filter + Duplicate Guard", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -650, 70 ] }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 2 }, "conditions": [ { "id": "if-7", "leftValue": "={{ $json.should_render }}", "rightValue": true, "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "id": "public-8", "name": "Pass Content Filter?", "type": "n8n-nodes-base.if", "typeVersion": 2, "position": [ -420, 70 ] }, { "parameters": { "mode": "runOnceForEachItem", "language": "javaScript", "jsCode": "return {json:{status:'skipped',title:$json.title,dedupe_key:$json.dedupe_key,reasons:$json.skip_reasons}};" }, "id": "public-9", "name": "Skipped Item — Report", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -200, 230 ] }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 2 }, "conditions": [ { "id": "if-10", "leftValue": "={{ $json.config.ai }}", "rightValue": true, "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "id": "public-11", "name": "AI Enrichment Enabled?", "type": "n8n-nodes-base.if", "typeVersion": 2, "position": [ -200, 20 ] }, { "parameters": { "method": "POST", "url": "={{ $json.config.aiUrl }}", "sendBody": true, "contentType": "json", "specifyBody": "json", "jsonBody": "={{ JSON.stringify({ task: \"Create a concise video script. Return JSON with optional title, summary, script.\", item: { title: $json.title, summary: $json.summary, article_url: $json.article_url, source_name: $json.source_name } }) }}", "options": { "response": { "response": { "responseFormat": "json" } } } }, "id": "public-12", "name": "AI Script Provider Webhook", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 20, -70 ], "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueRegularOutput" }, { "parameters": { "mode": "runOnceForEachItem", "language": "javaScript", "jsCode": "const ai=$input.item.json||{},x=$('Filter + Duplicate Guard').first().json,cut=(v,n)=>String(v||'').replace(/\\s+/g,' ').trim().slice(0,n);return {json:{...x,content:{title:cut(ai.title||x.title,100),summary:cut(ai.summary||x.summary,500),script:cut(ai.script||ai.summary||x.summary,900),enrichment:'ai-webhook'}}};" }, "id": "public-13", "name": "Normalize AI Output", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 250, -70 ] }, { "parameters": { "mode": "runOnceForEachItem", "language": "javaScript", "jsCode": "const x=$input.item.json,cut=(v,n)=>String(v||'').replace(/\\s+/g,' ').trim().slice(0,n);return {json:{...x,content:{title:cut(x.title,100),summary:cut(x.summary,500),script:cut(x.summary,900),enrichment:'feed'}}};" }, "id": "public-14", "name": "Use Feed Summary", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 20, 110 ] }, { "parameters": { "mode": "runOnceForEachItem", "language": "javaScript", "jsCode": "const x=$input.item.json;return {json:{...x,payload:{content_type:'rss-summary',headline:x.content.title,summary:x.content.summary,script:x.content.script,source_url:x.article_url||'',source_name:x.source_name||'',published_at:x.published_at||'',author:x.author||''}}};" }, "id": "public-15", "name": "Prepare Rendofy Payload", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 480, 20 ] }, { "parameters": { "method": "POST", "url": "https://api.rendofy.com/webhook/render-intake", "sendBody": true, "contentType": "json", "specifyBody": "json", "jsonBody": "={{ JSON.stringify({ api_key: $env.RENDOFY_API_KEY, callback_url: $execution.resumeUrl, payload: $json.payload }) }}", "options": { "response": { "response": { "responseFormat": "json" } } } }, "id": "public-16", "name": "Submit Render to Rendofy", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 700, 20 ], "onError": "continueRegularOutput" }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 2 }, "conditions": [ { "id": "if-17", "leftValue": "={{ $json.status === \"queued\" && !!$json.job_id }}", "rightValue": true, "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "id": "public-18", "name": "Render Request Accepted?", "type": "n8n-nodes-base.if", "typeVersion": 2, "position": [ 920, 20 ] }, { "parameters": { "mode": "runOnceForEachItem", "language": "javaScript", "jsCode": "return {json:{status:'request_error',error:$json.error||'Rendofy did not return queued status and job_id.',retry_guidance:'Do not automatically resubmit; first verify whether Rendofy accepted the original job.'}};" }, "id": "public-19", "name": "Render Request Error — Stop", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1140, 160 ] }, { "parameters": { "resume": "webhook", "httpMethod": "POST", "responseMode": "onReceived", "options": {} }, "id": "public-20", "name": "Wait for Rendofy Callback", "type": "n8n-nodes-base.wait", "typeVersion": 1.1, "position": [ 1140, 0 ] }, { "parameters": { "mode": "runOnceForEachItem", "language": "javaScript", "jsCode": "const envelope=$input.item.json;const callback=envelope&&typeof envelope.body==='object'&&envelope.body!==null?envelope.body:{};return {json:callback};" }, "id": "public-21", "name": "Normalize Rendofy Callback", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1360, 0 ] }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 2 }, "conditions": [ { "id": "if-22", "leftValue": "={{ $json.status === \"completed\" && !!$json.video_url }}", "rightValue": true, "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "id": "public-23", "name": "Render Completed with Video URL?", "type": "n8n-nodes-base.if", "typeVersion": 2, "position": [ 1580, 0 ] }, { "parameters": { "mode": "runOnceForEachItem", "language": "javaScript", "jsCode": "const c=$input.item.json,x=$('Filter + Duplicate Guard').first().json;return {json:{status:c.status||'failed',job_id:c.job_id||'',error:c.error||'Missing completed video_url.',dedupe_key:x.dedupe_key,retry_guidance:'Do not blindly resubmit. Confirm the prior job status to avoid duplicate renders.'}};" }, "id": "public-24", "name": "Render Failed — Retry-Safe Context", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1800, 170 ] }, { "parameters": { "url": "={{ $json.video_url }}", "options": { "response": { "response": { "responseFormat": "file", "outputPropertyName": "video" } } } }, "id": "public-25", "name": "Download Rendered MP4", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 1800, -20 ], "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueErrorOutput" }, { "parameters": { "mode": "runOnceForEachItem", "language": "javaScript", "jsCode": "const c=$input.item.json,x=$('Filter + Duplicate Guard').first().json,p=$('Prepare Rendofy Payload').first().json,state=$getWorkflowStaticData('global');state.rendofyCompletedItems=state.rendofyCompletedItems||{};state.rendofyCompletedItems[x.dedupe_key]=Date.now();let targets=[];try{targets=JSON.parse(x.config.targets)}catch{} if(!Array.isArray(targets))targets=[];targets=targets.filter(t=>t&&typeof t.name==='string'&&typeof t.webhook_url==='string'&&/^https:\\/\\//i.test(t.webhook_url));return {json:{status:'completed',job_id:c.job_id||'',video_url:c.video_url||'',title:p.content.title,summary:p.content.summary,script:p.content.script,publish_enabled:x.config.publish,publish_targets:targets}};" }, "id": "public-26", "name": "Mark Complete + Prepare Publishing", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 2040, -20 ] }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 2 }, "conditions": [ { "id": "if-27", "leftValue": "={{ $json.publish_enabled && $json.publish_targets.length > 0 }}", "rightValue": true, "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} }, "id": "public-28", "name": "Publishing Enabled?", "type": "n8n-nodes-base.if", "typeVersion": 2, "position": [ 2260, -20 ] }, { "parameters": { "mode": "runOnceForEachItem", "language": "javaScript", "jsCode": "return {json:{...$input.item.json,publishing:'disabled_or_no_valid_targets'}};" }, "id": "public-29", "name": "Publishing Disabled — MP4 Ready", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 2480, 100 ] }, { "parameters": { "mode": "runOnceForEachItem", "language": "javaScript", "jsCode": "return $input.item.json.publish_targets.map(publish_target=>({json:{...$input.item.json,publish_target}}));" }, "id": "public-30", "name": "Create Publish Jobs", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 2480, -90 ] }, { "parameters": { "method": "POST", "url": "={{ $json.publish_target.webhook_url }}", "sendBody": true, "contentType": "json", "specifyBody": "json", "jsonBody": "={{ JSON.stringify({ platform: $json.publish_target.name, job_id: $json.job_id, video_url: $json.video_url, title: $json.title, summary: $json.summary, script: $json.script, idempotency_key: $json.job_id }) }}", "options": { "response": { "response": { "responseFormat": "json" } } } }, "id": "public-31", "name": "Publish to Configured Platform", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 2700, -90 ], "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueRegularOutput" }, { "parameters": { "mode": "runOnceForEachItem", "language": "javaScript", "jsCode": "return {json:{status:'publish_attempted',response:$input.item.json}};" }, "id": "public-32", "name": "Publishing Result", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 2920, -90 ] }, { "parameters": { "mode": "runOnceForEachItem", "language": "javaScript", "jsCode": "const callback = $('Normalize Rendofy Callback').first().json;\nreturn { json: { status: 'download_error', job_id: callback.job_id || '', video_url: callback.video_url || '', error: $input.item.json.error || 'MP4 download failed after retries.', retry_guidance: 'Retry the download with the existing video_url; do not resubmit the render.' } };" }, "id": "public-download-error", "name": "Download Error — Not Marked Complete", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 2040, 150 ] } ], "connections": { "RSS Feed Trigger": { "main": [ [ { "node": "Normalize Feed Item", "type": "main", "index": 0 } ], [] ] }, "Normalize Feed Item": { "main": [ [ { "node": "Filter + Duplicate Guard", "type": "main", "index": 0 } ], [] ] }, "Filter + Duplicate Guard": { "main": [ [ { "node": "Pass Content Filter?", "type": "main", "index": 0 } ], [] ] }, "Pass Content Filter?": { "main": [ [ { "node": "AI Enrichment Enabled?", "type": "main", "index": 0 } ], [ { "node": "Skipped Item — Report", "type": "main", "index": 0 } ] ] }, "AI Enrichment Enabled?": { "main": [ [ { "node": "AI Script Provider Webhook", "type": "main", "index": 0 } ], [ { "node": "Use Feed Summary", "type": "main", "index": 0 } ] ] }, "AI Script Provider Webhook": { "main": [ [ { "node": "Normalize AI Output", "type": "main", "index": 0 } ], [] ] }, "Normalize AI Output": { "main": [ [ { "node": "Prepare Rendofy Payload", "type": "main", "index": 0 } ], [] ] }, "Use Feed Summary": { "main": [ [ { "node": "Prepare Rendofy Payload", "type": "main", "index": 0 } ], [] ] }, "Prepare Rendofy Payload": { "main": [ [ { "node": "Submit Render to Rendofy", "type": "main", "index": 0 } ], [] ] }, "Submit Render to Rendofy": { "main": [ [ { "node": "Render Request Accepted?", "type": "main", "index": 0 } ], [] ] }, "Render Request Accepted?": { "main": [ [ { "node": "Wait for Rendofy Callback", "type": "main", "index": 0 } ], [ { "node": "Render Request Error — Stop", "type": "main", "index": 0 } ] ] }, "Wait for Rendofy Callback": { "main": [ [ { "node": "Normalize Rendofy Callback", "type": "main", "index": 0 } ], [] ] }, "Normalize Rendofy Callback": { "main": [ [ { "node": "Render Completed with Video URL?", "type": "main", "index": 0 } ], [] ] }, "Render Completed with Video URL?": { "main": [ [ { "node": "Download Rendered MP4", "type": "main", "index": 0 } ], [ { "node": "Render Failed — Retry-Safe Context", "type": "main", "index": 0 } ] ] }, "Download Rendered MP4": { "main": [ [ { "node": "Mark Complete + Prepare Publishing", "type": "main", "index": 0 } ], [ { "node": "Download Error — Not Marked Complete", "type": "main", "index": 0 } ] ] }, "Mark Complete + Prepare Publishing": { "main": [ [ { "node": "Publishing Enabled?", "type": "main", "index": 0 } ], [] ] }, "Publishing Enabled?": { "main": [ [ { "node": "Create Publish Jobs", "type": "main", "index": 0 } ], [ { "node": "Publishing Disabled — MP4 Ready", "type": "main", "index": 0 } ] ] }, "Create Publish Jobs": { "main": [ [ { "node": "Publish to Configured Platform", "type": "main", "index": 0 } ], [] ] }, "Publish to Configured Platform": { "main": [ [ { "node": "Publishing Result", "type": "main", "index": 0 } ], [] ] } }, "pinData": {}, "settings": { "executionOrder": "v1" }, "active": false }