Workflow: Youtube Workflow
Integrations: emailSend, filter, httpRequest, if, rssFeedRead, scheduleTrigger, splitOut, stickyNote, stopAndError, youTube

{"meta": {"instanceId": "workflow-17488b78", "versionId": "1.0.0", "createdAt": "2025-09-29T07:07:55.383329", "updatedAt": "2025-09-29T07:07:55.383354", "owner": "n8n-user", "license": "MIT", "category": "automation", "status": "active", "priority": "high", "environment": "production"}, "nodes": [{"id": "a5292068-5ace-4372-9869-46100ae81b8f", "name": "Get video details", "type": "n8n-nodes-base.youTube", "notes": "Make a call to the YouTube API so that we have the thumbnail for the email and the duration to filter out shorts.", "position": [1000, -60], "parameters": {"part": ["contentDetails", "snippet", "id"], "options": {}, "videoId": "={{ $json.id.replace(\"yt:video:\", \"\") }}", "resource": "video", "operation": "get"}, "credentials": {"youTubeOAuth2Api": {"id": "5lD8Hahvq4r7Og0F", "name": "YouTube account"}}, "typeVersion": 1}, {"id": "b9eb34aa-90c4-492a-a33e-37a32812fa32", "name": "Schedule Trigger", "type": "n8n-nodes-base.scheduleTrigger", "position": [-840, -160], "parameters": {"rule": {"interval": [{"field": "hours", "hoursInterval": 1, "triggerAtMinute": 47}]}}, "typeVersion": 1.2, "notes": "This scheduleTrigger node performs automated tasks as part of the workflow."}, {"id": "8f0dbe74-53e5-4b14-86f6-eb0f502c8471", "name": "Filter out shorts", "type": "n8n-nodes-base.if", "notes": "Sometime, some live broadcasts that are then posted as regular videos do not have a duration. That is why we check if `duration` is present in `contentDetails`.", "position": [1180, -60], "parameters": {"options": {}, "conditions": {"options": {"version": 1, "leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "or", "conditions": [{"id": "5342ecc0-d764-4bef-8161-d1f571fcb931", "operator": {"type": "string", "operation": "notExists", "singleValue": true}, "leftValue": "={{ $json.contentDetails.duration }}", "rightValue": "\"duration\""}, {"id": "b82e3373-a28b-49bd-afa0-4f48cafe2bfe", "operator": {"type": "number", "operation": "gt"}, "leftValue": "={{ Duration.fromISO($json.contentDetails.duration).as('seconds') }}", "rightValue": 61}]}}, "notesInFlow": false, "typeVersion": 2}, {"id": "14d54ed0-f5c0-4992-af56-0af2d8973963", "name": "Sticky Note", "type": "n8n-nodes-base.stickyNote", "position": [-900, -340], "parameters": {"color": 7, "width": 220, "height": 460, "content": "### Default frequency: every hour\nChanging it here is enough if you want to check for new videos at a higher or lower frequency. You don't have to edit anything else."}, "typeVersion": 1, "notes": "This stickyNote node performs automated tasks as part of the workflow."}, {"id": "c4acbb10-1f57-4934-a324-f26d0532767c", "name": "Sticky Note1", "type": "n8n-nodes-base.stickyNote", "position": [-660, -340], "parameters": {"color": 5, "width": 880, "height": 460, "content": "### Get my subscriptions from the YouTube Data v3 API\nYou can expect to use 1 quota per 50 subscriptions per run, which is well within the 10 000/req a day allowed by default."}, "typeVersion": 1, "notes": "This stickyNote node performs automated tasks as part of the workflow."}, {"id": "4ae2d2f3-53b5-4431-90d8-06e41a6950e2", "name": "Sticky Note2", "type": "n8n-nodes-base.stickyNote", "position": [480, -160], "parameters": {"color": 4, "width": 440, "height": 280, "content": "### Get the 15 latest videos of each channel with RSS\nUsing the YouTube API instead would cost too many quotas to make it viable."}, "typeVersion": 1, "notes": "This stickyNote node performs automated tasks as part of the workflow."}, {"id": "48894d79-7e59-49fc-beb5-445fb5ca2ff6", "name": "Sticky Note3", "type": "n8n-nodes-base.stickyNote", "position": [940, -160], "parameters": {"color": 3, "width": 400, "height": 280, "content": "### Call YouTube's API for more data\nWe need the thumbnails for the email and the duration to filter out shorts."}, "typeVersion": 1, "notes": "This stickyNote node performs automated tasks as part of the workflow."}, {"id": "e3da3f97-138c-481e-a763-9a3c9e402928", "name": "Sticky Note4", "type": "n8n-nodes-base.stickyNote", "position": [1360, -160], "parameters": {"color": 6, "width": 260, "height": 280, "content": "### Configure your email here\nTo go to the video from the email, simply click on the thumbnail."}, "typeVersion": 1, "notes": "This stickyNote node performs automated tasks as part of the workflow."}, {"id": "0d092c3d-b2e1-4468-a044-c6cf0f37672b", "name": "Get latest 15 videos of each channel", "type": "n8n-nodes-base.rssFeedRead", "notes": "YouTube provides an RSS feed for each channel with the 15 latest videos.\nWe use this instead of the YouTube Data v3 API, as search requests cost a lot of \"quota points\" and would easily put us over the daily limit with just one workflow run.", "position": [540, -60], "parameters": {"url": "{{ $env.BASE_URL }}", "options": {}}, "typeVersion": 1.1}, {"id": "34823384-d8a5-415a-87ff-203d65aa9a75", "name": "Get my subscriptions", "type": "n8n-nodes-base.httpRequest", "notes": "Get subscriptions from YouTube Data v3 API", "position": [-600, -160], "parameters": {"url": "{{ $env.API_BASE_URL }}", "options": {"pagination": {"pagination": {"parameters": {"parameters": [{"name": "pageToken", "value": "={{ $response.body.nextPageToken }}"}]}, "completeExpression": "={{ !('nextPageToken' in $response.body) }}", "paginationCompleteWhen": "other"}}}, "sendQuery": true, "authentication": "{{ $credentials.predefinedCredentialType }}", "queryParameters": {"parameters": [{"name": "mine", "value": "true"}, {"name": "part", "value": "snippet,contentDetails"}, {"name": "maxResults", "value": "50"}]}, "nodeCredentialType": "YOUR_CREDENTIAL_HERE"}, "credentials": {"youTubeOAuth2Api": {"id": "5lD8Hahvq4r7Og0F", "name": "YouTube account"}}, "notesInFlow": true, "typeVersion": 4.2}, {"id": "534e38f3-ac40-4194-8821-5926ee581605", "name": "Check for errors", "type": "n8n-nodes-base.if", "position": [-400, -160], "parameters": {"options": {}, "conditions": {"options": {"version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "and", "conditions": [{"id": "5972ff90-aa5a-470c-aa96-87138eb60565", "operator": {"type": "object", "operation": "exists", "singleValue": true}, "leftValue": "={{ $json.error }}", "rightValue": "error"}]}}, "typeVersion": 2.2, "notes": "This if node performs automated tasks as part of the workflow."}, {"id": "2d872c0f-30b9-4ffc-aba0-6644bf05d7bb", "name": "Only keep channels with unwatched videos", "type": "n8n-nodes-base.filter", "notes": "It's not a perfect indicator for new videos but helps reduce the amount of channels to process.", "position": [40, -60], "parameters": {"options": {}, "conditions": {"options": {"version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "and", "conditions": [{"id": "4734ee8c-1655-47be-bd45-a9527aee2833", "operator": {"type": "number", "operation": "gt"}, "leftValue": "={{ $json.contentDetails.newItemCount }}", "rightValue": 0}]}}, "typeVersion": 2.2}, {"id": "c7bd97ec-47c1-40b4-955d-bf89d3cde330", "name": "Keep only videos published since last run", "type": "n8n-nodes-base.filter", "notes": "We dynamically figure out the last run's execution time through the settings of the \"Schedule Trigger\" node.", "position": [740, -60], "parameters": {"options": {}, "conditions": {"options": {"version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "and", "conditions": [{"id": "65d905a2-c89e-41f3-a2cf-0d1a76c48d8e", "operator": {"type": "dateTime", "operation": "after"}, "leftValue": "={{ $json.pubDate.toDateTime() }}", "rightValue": "={{ \n  $('Schedule Trigger').item.json.timestamp.toDateTime().minus(\n    $('Schedule Trigger').params.rule.interval[0].hoursInterval,\n    $('Schedule Trigger').params.rule.interval[0].field\n  ).toISO()\n}}"}]}}, "typeVersion": 2.2}, {"id": "72341b1f-a391-4210-b3ca-4e74ae1f2e1b", "name": "Send an email for each new video", "type": "n8n-nodes-base.emailSend", "notes": "The expression in the HTML for the thumbnail simply selects the last element of the thumbnails array so that we get the best possible resolution thumbnail available.", "position": [1440, -60], "webhookId": "44bf0e95-98e5-4b5b-a7c5-c802379ab3b0", "parameters": {"html": "=<h1 style=\"text-align: center;\">{{ $json.snippet.title }}</h1>\n<a href=\"{{ $env.WEBHOOK_URL }}{{ $json.id }}\">\n  <img src=\"{{ $json.snippet.thumbnails[Object.keys($json.snippet.thumbnails)[Object.keys($json.snippet.thumbnails).length - 1]].url }}\" alt=\"Watch on YouTube\" style=\"width:100%; height:auto; max-width:640px; display:block; margin: 10px auto;\">\n</a>", "options": {"appendAttribution": false}, "subject": "={{ $json.snippet.channelTitle }}", "toEmail": "My Name <to@email.com>", "fromEmail": "YouTube <from@email.com>"}, "credentials": {"smtp": {"id": "ThrKm6bLUg1owKn1", "name": "SMTP account"}}, "notesInFlow": false, "typeVersion": 2.1}, {"id": "b82cfbd5-71e3-418f-9b6d-6d0ec007733a", "name": "If the HTTP request failed, throw the error", "type": "n8n-nodes-base.stopAndError", "position": [-180, -260], "parameters": {"errorMessage": "=Status code: {{ $json.error.code }}\nMessage: {{ $json.error.message }}"}, "typeVersion": 1, "notes": "This stopAndError node performs automated tasks as part of the workflow."}, {"id": "e89eca92-896f-46b5-8a4b-149d51682faa", "name": "Split out subscriptions to process individually", "type": "n8n-nodes-base.splitOut", "position": [-180, -60], "parameters": {"options": {}, "fieldToSplitOut": "items"}, "typeVersion": 1, "notes": "This splitOut node performs automated tasks as part of the workflow."}, {"id": "0e00fda6-1489-4c1a-8205-22e620a554c5", "name": "Sticky Note5", "type": "n8n-nodes-base.stickyNote", "position": [240, -240], "parameters": {"width": 220, "height": 360, "content": "## Manually filter out channels\nTo find the channel ID of a channel, click on the description \u2192 Share channel \u2192 Copy channel ID"}, "typeVersion": 1, "notes": "This stickyNote node performs automated tasks as part of the workflow."}, {"id": "bcc2e57c-23b2-42b7-81ab-cdd88b70b8a3", "name": "Filter out channels", "type": "n8n-nodes-base.filter", "notes": "Optional step", "position": [300, -60], "parameters": {"options": {}, "conditions": {"options": {"version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "and", "conditions": [{"id": "b27b14a9-c86c-4ebd-8a0f-4e7db722796e", "operator": {"type": "array", "operation": "notContains", "rightType": "any"}, "leftValue": "={{[\n  \"exampleChannelId1\",\n  \"exampleChannelId2\"\n]}}", "rightValue": "={{ $json.snippet.resourceId.channelId }}"}]}}, "notesInFlow": true, "typeVersion": 2.2}], "pinData": {}, "connections": {"34823384-d8a5-415a-87ff-203d65aa9a75": {"main": [[{"node": "error-handler-34823384-d8a5-415a-87ff-203d65aa9a75-362dbf47", "type": "main", "index": 0}], [{"node": "error-handler-34823384-d8a5-415a-87ff-203d65aa9a75-9a8d7403", "type": "main", "index": 0}], [{"node": "error-handler-34823384-d8a5-415a-87ff-203d65aa9a75-2c223667", "type": "main", "index": 0}], [{"node": "error-handler-34823384-d8a5-415a-87ff-203d65aa9a75-09c1d781", "type": "main", "index": 0}], [{"node": "error-handler-34823384-d8a5-415a-87ff-203d65aa9a75-10c86517", "type": "main", "index": 0}], [{"node": "error-handler-34823384-d8a5-415a-87ff-203d65aa9a75-46fbcc5e", "type": "main", "index": 0}], [{"node": "error-handler-34823384-d8a5-415a-87ff-203d65aa9a75-dce47a23", "type": "main", "index": 0}], [{"node": "error-handler-34823384-d8a5-415a-87ff-203d65aa9a75-747f85e6", "type": "main", "index": 0}]]}, "72341b1f-a391-4210-b3ca-4e74ae1f2e1b": {"main": [[{"node": "error-handler-72341b1f-a391-4210-b3ca-4e74ae1f2e1b-8c757746", "type": "main", "index": 0}], [{"node": "error-handler-72341b1f-a391-4210-b3ca-4e74ae1f2e1b-3fb8e349", "type": "main", "index": 0}], [{"node": "error-handler-72341b1f-a391-4210-b3ca-4e74ae1f2e1b-afa2ca27", "type": "main", "index": 0}], [{"node": "error-handler-72341b1f-a391-4210-b3ca-4e74ae1f2e1b-b440dd44", "type": "main", "index": 0}], [{"node": "error-handler-72341b1f-a391-4210-b3ca-4e74ae1f2e1b-d7b3181c", "type": "main", "index": 0}], [{"node": "error-handler-72341b1f-a391-4210-b3ca-4e74ae1f2e1b-ab3eab46", "type": "main", "index": 0}], [{"node": "error-handler-72341b1f-a391-4210-b3ca-4e74ae1f2e1b-a2cff4b9", "type": "main", "index": 0}], [{"node": "error-handler-72341b1f-a391-4210-b3ca-4e74ae1f2e1b-adb6b5c5", "type": "main", "index": 0}]]}}, "name": "Youtube Workflow", "settings": {"executionOrder": "v1", "saveManualExecutions": true, "callerPolicy": "workflowsFromSameOwner", "errorWorkflow": null, "timezone": "UTC", "executionTimeout": 3600, "maxExecutions": 1000, "retryOnFail": true, "retryCount": 3, "retryDelay": 1000}, "description": "Automated workflow: Youtube Workflow. This workflow integrates 10 different services: stickyNote, youTube, httpRequest, filter, scheduleTrigger. It contains 20 nodes and follows best practices for error handling and security.", "tags": ["automation", "n8n", "production-ready", "excellent", "optimized"], "notes": "Excellent quality workflow: Youtube Workflow. This workflow has been optimized for production use with comprehensive error handling, security, and documentation."}