The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "TMH \u2014 Auto Blog Publisher",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "hours",
"hoursInterval": 6
}
]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
208,
96
],
"id": "4a675d1e-c494-49c5-a7c2-52bef6e3c910",
"name": "Every 6 Hours"
},
{
"parameters": {
"url": "=https://api.github.com/repos/NJacobs415/manageher-makeover/contents/public/blog/{{ $json.slug }}.json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "githubApi",
"options": {
"response": {
"response": {
"neverError": true
}
}
}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
880,
0
],
"id": "09f1ab8e-50be-49a6-a5b1-a54a10ab7f98",
"name": "Check If Blog Exists",
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
},
"githubApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "check-404",
"leftValue": "={{ $json.message }}",
"rightValue": "Not Found",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
1104,
0
],
"id": "1361d2bd-6085-4134-8cf4-1a93f93023f7",
"name": "Is New Episode?"
},
{
"parameters": {
"method": "POST",
"url": "https://www.youtube-transcript.io/api/transcripts",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={\"ids\":[\"{{ $(\"Code in JavaScript\").item.json.id }}\"]}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
1328,
0
],
"id": "88e5aca6-484c-417b-ac29-4806578b4106",
"name": "Fetch Transcript",
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"onError": "continueRegularOutput"
},
{
"parameters": {
"jsCode": "// Combine transcript segments into clean text\nconst episode = $('Code in JavaScript').item.json;\nlet transcript = '';\n\ntry {\n const transcriptData = $input.first().json;\n \n // Handle different transcript response formats\n if (transcriptData.transcripts) {\n // youtube-transcript.io format\n transcript = transcriptData.transcripts\n .map(t => t.text || t.snippet || '')\n .join(' ')\n .replace(/\\s+/g, ' ')\n .trim();\n } else if (transcriptData.transcript) {\n // Alternative format\n if (Array.isArray(transcriptData.transcript)) {\n transcript = transcriptData.transcript\n .map(t => t.text || t.snippet || '')\n .join(' ')\n .replace(/\\s+/g, ' ')\n .trim();\n } else {\n transcript = transcriptData.transcript;\n }\n } else if (Array.isArray(transcriptData)) {\n transcript = transcriptData\n .map(t => t.text || t.snippet || '')\n .join(' ')\n .replace(/\\s+/g, ' ')\n .trim();\n }\n} catch(e) {\n // If transcript fetch failed, use the video description as fallback\n transcript = episode.description || 'Transcript not available.';\n}\n\n// Truncate if too long (Claude has context limits)\nif (transcript.length > 80000) {\n transcript = transcript.substring(0, 80000) + '... [transcript truncated]';\n}\n\nreturn [{ json: { ...episode, transcript } }];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1552,
0
],
"id": "cb44f4db-ffea-4670-9a4d-9c65f6cd94d5",
"name": "Clean Transcript"
},
{
"parameters": {
"method": "POST",
"url": "https://api.anthropic.com/v1/messages",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "anthropic-version",
"value": "2023-06-01"
},
{
"name": "content-type",
"value": "application/json"
}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={{ $json.claudeRequestBody }}",
"options": {
"response": {
"response": {
"neverError": true
}
}
}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
2000,
0
],
"id": "d287c296-2d66-427f-b164-2a56f5fc6d17",
"name": "Claude: Generate Blog Post",
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Parse Claude's tool_use response. Anthropic guarantees the input\n// object matches the tool's input_schema, so JSON parse failures from\n// unescaped quotes in free-text JSON (the original bug) are impossible.\nconst episode = $('Clean Transcript').item.json;\nconst claudeResponse = $input.first().json;\n\nif (!Array.isArray(claudeResponse.content)) {\n throw new Error('Claude response missing content array: ' + JSON.stringify(claudeResponse).slice(0, 400));\n}\nconst toolUse = claudeResponse.content.find(b => b.type === 'tool_use' && b.name === 'generate_blog_post');\nif (!toolUse) {\n throw new Error('Claude did not return a generate_blog_post tool_use block. stop_reason=' +\n (claudeResponse.stop_reason || 'unknown') + ' content_types=' +\n JSON.stringify(claudeResponse.content.map(b => b.type)));\n}\nconst blogData = toolUse.input;\nif (!blogData || typeof blogData !== 'object') {\n throw new Error('tool_use.input is not an object: ' + JSON.stringify(toolUse).slice(0, 400));\n}\n\n// Build the complete blog post object (same shape as before).\nconst post = {\n slug: episode.slug,\n title: blogData.title || episode.title,\n episodeNumber: blogData.episodeNumber || 0,\n guestName: blogData.guestName || '',\n guestBio: blogData.guestBio || '',\n publishedAt: episode.publishedAt,\n duration: episode.durationFormatted,\n thumbnail: episode.thumbnail,\n youtubeUrl: episode.youtubeUrl,\n spotifyUrl: 'https://open.spotify.com/show/03FuFRyzkaWhZkk5yxFePJ',\n appleUrl: 'https://podcasts.apple.com/us/podcast/the-manage-her/id1809208475',\n excerpt: blogData.excerpt || '',\n metaDescription: blogData.metaDescription || '',\n topics: blogData.topics || [],\n keyTakeaways: blogData.keyTakeaways || [],\n guestLinks: blogData.guestLinks || [],\n pullQuotes: blogData.pullQuotes || [],\n timestamps: blogData.timestamps || [],\n quiz: blogData.quiz || null,\n transcript: $('Clean Transcript').first().json.transcript || '',\n content: blogData.content || ''\n};\n\nconst indexEntry = {\n slug: post.slug,\n title: post.title,\n episodeNumber: post.episodeNumber,\n guestName: post.guestName,\n publishedAt: post.publishedAt,\n duration: post.duration,\n thumbnail: post.thumbnail,\n excerpt: post.excerpt,\n topics: post.topics,\n youtubeUrl: post.youtubeUrl\n};\n\nreturn [{ json: { post, indexEntry, slug: episode.slug } }];\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2224,
0
],
"id": "e1aeff75-6570-4a4f-98c1-c0fcaf81deeb",
"name": "Build Blog JSON"
},
{
"parameters": {
"content": "## TMH Auto Blog Publisher\n\n### FLOW:\n1. Fetches 5 most recent medium-length videos\n2. Filters to full episodes (30+ min)\n3. Checks if blog already exists in GitHub\n4. If new: fetches transcript\n5. Sends to Claude API for blog generation\n6. Commits blog post JSON to GitHub\n7. Updates posts.json index\n8. Cloudflare Pages auto-deploys\n\n### SETUP:\n1. Add GitHub PAT credential (Header Auth)\n2. Add Anthropic API credential\n3. Update credential IDs in nodes\n4. Test with manual trigger first\n\n### TRANSCRIPT:\nUses free transcript API.\nFalls back to video description if unavailable."
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
0,
0
],
"id": "d9daf4b9-8edd-46f1-83e6-b26edfc0222a",
"name": "Instructions"
},
{
"parameters": {
"url": "https://n8n.srv1075406.hstgr.cloud/webhook/tmh-youtube-episodes",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
432,
0
],
"id": "5c57dcdf-99d3-4dac-8fee-b3755efc5c25",
"name": "HTTP Request"
},
{
"parameters": {
"jsCode": "// Process episodes from the webhook response\nconst episodes = $input.first().json.episodes || [];\n\nfunction makeSlug(title) {\n return title\n .toLowerCase()\n .replace(/^ep\\.?\\s*\\d+\\s*:?\\s*/i, '')\n .replace(/\\|.*$/g, '')\n .replace(/the manage her:?\\s*/gi, '')\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .substring(0, 120);\n}\n\nfunction formatDuration(iso) {\n const match = (iso || '').match(/PT(?:(\\d+)H)?(?:(\\d+)M)?(?:(\\d+)S)?/);\n if (!match) return '30 min';\n const h = parseInt(match[1]||0);\n const m = parseInt(match[2]||0);\n if (h > 0) return `${h}h ${m}m`;\n return `${m} min`;\n}\n\n// Filter out YouTube Shorts (under 120 seconds)\nconst filtered = episodes.filter(ep => {\n const match = (ep.duration || '').match(/PT(?:(\\d+)H)?(?:(\\d+)M)?(?:(\\d+)S)?/);\n if (!match) return true;\n const totalSeconds = (parseInt(match[1]||0) * 3600) + (parseInt(match[2]||0) * 60) + parseInt(match[3]||0);\n return totalSeconds >= 120;\n});\n\n// Return each episode as a separate item\nreturn filtered.slice(0, 5).map(ep => ({\n json: {\n id: ep.id,\n title: ep.title,\n slug: makeSlug(ep.title),\n description: ep.description,\n thumbnail: ep.thumbnailMax || ep.thumbnail,\n publishedAt: ep.publishedAt,\n duration: ep.duration,\n durationFormatted: formatDuration(ep.duration),\n youtubeUrl: `https://www.youtube.com/watch?v=${ep.id}`\n }\n}));\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
656,
0
],
"id": "0688b309-1c14-4e9d-87ae-4c4b8a2a24a9",
"name": "Code in JavaScript"
},
{
"parameters": {
"jsCode": "const episode = $input.first().json;\n\nconst prompt = `You are a blog content writer for The Manage Her\u00ae \u2014 a women's leadership podcast hosted by Aimee Rickabus, CEO of a nine-figure technology company, bestselling author, and mother of six. Generate an enhanced show notes blog post from this podcast episode by calling the generate_blog_post tool.\n\nEPISODE DETAILS:\nTitle: ${episode.title}\nDuration: ${episode.durationFormatted}\nPublished: ${episode.publishedAt}\nYouTube URL: ${episode.youtubeUrl}\n\nEPISODE DESCRIPTION:\n${(episode.description || '').substring(0, 8000)}\n\nTRANSCRIPT:\n${(episode.transcript || '').substring(0, 50000)}\n\nWRITING GUIDANCE:\n- title: SEO-friendly, under 80 characters, different from the episode title\n- episodeNumber: extract from the title or use 0 if absent\n- guestName: full name or empty string for solo episodes\n- guestBio: 2-3 sentence bio based on what's discussed\n- excerpt: 2-3 sentence summary that makes someone want to listen\n- metaDescription: SEO meta description under 160 characters\n- topics: 3-5 from the enum (Leadership, Motherhood, Financial Literacy, Wellness, Entrepreneurship, Boundaries, Identity, Marriage, Community, Spirituality)\n- keyTakeaways: 5-7 complete-sentence takeaways\n- guestLinks: extract Instagram/LinkedIn/TikTok/YouTube/website URLs from the description\n- pullQuotes: 3-5 memorable quotes; timestamp empty if unknown\n- timestamps: chapter markers from the transcript/description\n- content: 800-1200 words of HTML using p/strong/em/h3 tags. Brand voice: bold, warm, direct, permission-giving. Address reader as \"you\". No bullet lists.\n- quiz: a SELF-DISCOVERY quiz (not knowledge test) with empowering archetype names tied to the episode. types.A/B/C/D each have a name (2-4 word archetype) and a 3-4 sentence personalized description in second person ending with why they should listen. questions: EXACTLY 6 questions, each with EXACTLY 4 options. Each option's type is A/B/C/D. All options should feel valid; this is self-discovery, not right/wrong.`;\n\nconst tool = {\n name: \"generate_blog_post\",\n description: \"Generate the structured blog post for this episode.\",\n input_schema: {\"type\":\"object\",\"additionalProperties\":false,\"required\":[\"title\",\"episodeNumber\",\"guestName\",\"excerpt\",\"metaDescription\",\"topics\",\"keyTakeaways\",\"content\",\"quiz\"],\"properties\":{\"title\":{\"type\":\"string\",\"maxLength\":100},\"episodeNumber\":{\"type\":\"integer\",\"minimum\":0},\"guestName\":{\"type\":\"string\"},\"guestBio\":{\"type\":\"string\"},\"excerpt\":{\"type\":\"string\",\"maxLength\":400},\"metaDescription\":{\"type\":\"string\",\"maxLength\":200},\"topics\":{\"type\":\"array\",\"minItems\":1,\"maxItems\":5,\"items\":{\"type\":\"string\",\"enum\":[\"Leadership\",\"Motherhood\",\"Financial Literacy\",\"Wellness\",\"Entrepreneurship\",\"Boundaries\",\"Identity\",\"Marriage\",\"Community\",\"Spirituality\"]}},\"keyTakeaways\":{\"type\":\"array\",\"minItems\":3,\"maxItems\":8,\"items\":{\"type\":\"string\"}},\"guestLinks\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"additionalProperties\":false,\"required\":[\"label\",\"url\"],\"properties\":{\"label\":{\"type\":\"string\"},\"url\":{\"type\":\"string\"}}}},\"pullQuotes\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"additionalProperties\":false,\"required\":[\"text\"],\"properties\":{\"text\":{\"type\":\"string\"},\"timestamp\":{\"type\":\"string\"}}}},\"timestamps\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"additionalProperties\":false,\"required\":[\"time\",\"label\"],\"properties\":{\"time\":{\"type\":\"string\"},\"label\":{\"type\":\"string\"}}}},\"content\":{\"type\":\"string\"},\"quiz\":{\"type\":\"object\",\"additionalProperties\":false,\"required\":[\"title\",\"description\",\"types\",\"questions\"],\"properties\":{\"title\":{\"type\":\"string\"},\"description\":{\"type\":\"string\"},\"types\":{\"type\":\"object\",\"additionalProperties\":false,\"required\":[\"A\",\"B\",\"C\",\"D\"],\"properties\":{\"A\":{\"type\":\"object\",\"additionalProperties\":false,\"required\":[\"name\",\"description\"],\"properties\":{\"name\":{\"type\":\"string\"},\"description\":{\"type\":\"string\"}}},\"B\":{\"type\":\"object\",\"additionalProperties\":false,\"required\":[\"name\",\"description\"],\"properties\":{\"name\":{\"type\":\"string\"},\"description\":{\"type\":\"string\"}}},\"C\":{\"type\":\"object\",\"additionalProperties\":false,\"required\":[\"name\",\"description\"],\"properties\":{\"name\":{\"type\":\"string\"},\"description\":{\"type\":\"string\"}}},\"D\":{\"type\":\"object\",\"additionalProperties\":false,\"required\":[\"name\",\"description\"],\"properties\":{\"name\":{\"type\":\"string\"},\"description\":{\"type\":\"string\"}}}}},\"questions\":{\"type\":\"array\",\"minItems\":6,\"maxItems\":6,\"items\":{\"type\":\"object\",\"additionalProperties\":false,\"required\":[\"question\",\"options\"],\"properties\":{\"question\":{\"type\":\"string\"},\"options\":{\"type\":\"array\",\"minItems\":4,\"maxItems\":4,\"items\":{\"type\":\"object\",\"additionalProperties\":false,\"required\":[\"text\",\"type\"],\"properties\":{\"text\":{\"type\":\"string\"},\"type\":{\"type\":\"string\",\"enum\":[\"A\",\"B\",\"C\",\"D\"]}}}}}}}}}}}\n};\n\nconst requestBody = {\n model: \"claude-sonnet-4-6\",\n max_tokens: 10000,\n tools: [tool],\n tool_choice: { type: \"tool\", name: \"generate_blog_post\" },\n messages: [{ role: \"user\", content: prompt }]\n};\n\nreturn [{ json: { ...episode, claudeRequestBody: JSON.stringify(requestBody) } }];\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1776,
0
],
"id": "7b164a58-19eb-4713-bbef-dd673f50095d",
"name": "Build Claude Request"
},
{
"parameters": {
"method": "POST",
"url": "https://api.indexnow.org/indexnow",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"host\": \"themanageher.com\",\n \"key\": \"a1b2c3d4e5f6g7h8\",\n \"keyLocation\": \"https://themanageher.com/a1b2c3d4e5f6g7h8.txt\",\n \"urlList\": [\"https://themanageher.com/blog/{{ $('Build Blog JSON').item.json.slug }}\"]\n}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
4464,
0
],
"id": "indexnow-ping-001",
"name": "Ping IndexNow",
"onError": "continueRegularOutput",
"retryOnFail": true,
"maxTries": 2,
"waitBetweenTries": 5000
},
{
"parameters": {
"url": "=https://services.leadconnectorhq.com/contacts/?locationId=JzYUXEAehZEve2vuOdqM&query={{ encodeURIComponent($('Build Blog JSON').item.json.post.guestName.replace(/^(Dr\\.?|Chef|Coach|Pastor|Rev\\.?|Professor|Prof\\.?)\\s+/i, '')) }}",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Version",
"value": "2021-07-28"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
4688,
0
],
"id": "ghl-lookup-guest-001",
"name": "Lookup Guest by Name",
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "tags-include-sent",
"leftValue": "={{ ($json.contacts && $json.contacts[0] && $json.contacts[0].tags) ? $json.contacts[0].tags : [] }}",
"rightValue": "guest-outreach-sent",
"operator": {
"type": "array",
"operation": "contains",
"rightType": "any"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
4912,
0
],
"id": "ghl-is-sent-001",
"name": "Is Already Sent?"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "has-contact-id",
"leftValue": "={{ ($('Lookup Guest by Name').item.json.contacts || []).length }}",
"rightValue": 0,
"operator": {
"type": "number",
"operation": "gt"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
5136,
0
],
"id": "ghl-has-existing-001",
"name": "Has Existing Contact?"
},
{
"parameters": {
"method": "PUT",
"url": "=https://services.leadconnectorhq.com/contacts/{{ $('Lookup Guest by Name').item.json.contacts[0].id }}",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Version",
"value": "2021-07-28"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"customFields\": [\n {\"id\": \"RyA70UVI5yUY83Wu5hYz\", \"field_value\": \"{{ $('Build Blog JSON').item.json.post.title }}\"},\n {\"id\": \"zRU07yCeCsSBQrkNZwdb\", \"field_value\": \"https://themanageher.com/blog/{{ $('Build Blog JSON').item.json.slug }}\"},\n {\"id\": \"lctcR3WExxz0pULDyokA\", \"field_value\": \"{{ $('Build Blog JSON').item.json.post.episodeNumber }}\"},\n {\"id\": \"HsP54mVLCSvKjPv2sGyO\", \"field_value\": \"{{ $('Build Blog JSON').item.json.post.quiz ? $('Build Blog JSON').item.json.post.quiz.title : '' }}\"},\n {\"id\": \"KX3Tvi2pvlrUCtlVhYDI\", \"field_value\": \"{{ new Date($('Build Blog JSON').item.json.post.publishedAt).toLocaleDateString('en-CA', { timeZone: 'America/Los_Angeles' }) }}\"}\n ]\n}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
5360,
-96
],
"id": "ghl-update-existing-001",
"name": "Update Existing Contact",
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"method": "POST",
"url": "=https://services.leadconnectorhq.com/contacts/{{ $('Lookup Guest by Name').item.json.contacts[0].id }}/tags",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Version",
"value": "2021-07-28"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"tags\": [\"podcast-guest\", \"guest-outreach-pending\"]\n}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
5584,
-96
],
"id": "ghl-add-tags-001",
"name": "Add Tags to Existing",
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"method": "POST",
"url": "https://services.leadconnectorhq.com/contacts/",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Version",
"value": "2021-07-28"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"locationId\": \"JzYUXEAehZEve2vuOdqM\",\n \"name\": \"{{ $('Build Blog JSON').item.json.post.guestName.replace(/^(Dr\\.?|Chef|Coach|Pastor|Rev\\.?|Professor|Prof\\.?)\\s+/i, '') }}\",\n \"firstName\": \"{{ $('Build Blog JSON').item.json.post.guestName.replace(/^(Dr\\.?|Chef|Coach|Pastor|Rev\\.?|Professor|Prof\\.?)\\s+/i, '').split(' ')[0] }}\",\n \"lastName\": \"{{ $('Build Blog JSON').item.json.post.guestName.replace(/^(Dr\\.?|Chef|Coach|Pastor|Rev\\.?|Professor|Prof\\.?)\\s+/i, '').split(' ').slice(1).join(' ') }}\",\n \"tags\": [\"podcast-guest\", \"guest-outreach-pending\"],\n \"customFields\": [\n {\"id\": \"RyA70UVI5yUY83Wu5hYz\", \"field_value\": \"{{ $('Build Blog JSON').item.json.post.title }}\"},\n {\"id\": \"zRU07yCeCsSBQrkNZwdb\", \"field_value\": \"https://themanageher.com/blog/{{ $('Build Blog JSON').item.json.slug }}\"},\n {\"id\": \"lctcR3WExxz0pULDyokA\", \"field_value\": \"{{ $('Build Blog JSON').item.json.post.episodeNumber }}\"},\n {\"id\": \"HsP54mVLCSvKjPv2sGyO\", \"field_value\": \"{{ $('Build Blog JSON').item.json.post.quiz ? $('Build Blog JSON').item.json.post.quiz.title : '' }}\"},\n {\"id\": \"KX3Tvi2pvlrUCtlVhYDI\", \"field_value\": \"{{ new Date($('Build Blog JSON').item.json.post.publishedAt).toLocaleDateString('en-CA', { timeZone: 'America/Los_Angeles' }) }}\"}\n ]\n}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
5360,
96
],
"id": "ghl-create-new-001",
"name": "Create New Contact",
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"url": "https://api.github.com/repos/NJacobs415/manageher-makeover/git/refs/heads/main",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "githubApi",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "application/vnd.github+json"
},
{
"name": "X-GitHub-Api-Version",
"value": "2022-11-28"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
2448,
0
],
"id": "a000aaaa-0001-0001-0001-000000000001",
"name": "GH: Get HEAD Ref",
"credentials": {
"githubApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"url": "=https://api.github.com/repos/NJacobs415/manageher-makeover/git/commits/{{ $json.object.sha }}",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "githubApi",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "application/vnd.github+json"
},
{
"name": "X-GitHub-Api-Version",
"value": "2022-11-28"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
2672,
0
],
"id": "a000aaaa-0002-0002-0002-000000000002",
"name": "GH: Get HEAD Commit",
"credentials": {
"githubApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"url": "https://api.github.com/repos/NJacobs415/manageher-makeover/contents/public/blog/posts.json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "githubApi",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "application/vnd.github+json"
},
{
"name": "X-GitHub-Api-Version",
"value": "2022-11-28"
}
]
},
"options": {
"response": {
"response": {
"fullResponse": true,
"neverError": true,
"responseFormat": "json"
}
}
}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
2896,
0
],
"id": "a000aaaa-0003-0003-0003-000000000003",
"name": "GH: Get posts.json (or 404)",
"credentials": {
"githubApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Strict 200/404 status-code split per atomic-commit design (v2).\n// fullResponse:true on the previous node gives us {statusCode, body, headers}.\n// Any status other than 200 or 404 throws \u2014 we do NOT silently\n// reset posts.json to an empty index on transient/auth errors.\nconst buildOut = $node[\"Build Blog JSON\"].json;\nconst headRef = $node[\"GH: Get HEAD Ref\"].json;\nconst headCommit = $node[\"GH: Get HEAD Commit\"].json;\nconst env = $input.first().json;\n\nif (!headRef?.object?.sha) throw new Error('GH: Get HEAD Ref returned no object.sha');\nif (!headCommit?.tree?.sha) throw new Error('GH: Get HEAD Commit returned no tree.sha');\n\nconst status = env?.statusCode;\nif (typeof status !== 'number') {\n throw new Error('posts.json fetch: missing statusCode (fullResponse not enabled?). Got: ' + JSON.stringify(env).slice(0,200));\n}\n\nlet existingPosts = [];\nif (status === 200) {\n const body = env.body;\n if (!body || typeof body.content !== 'string') {\n throw new Error('200 from posts.json fetch but body.content missing or not a string.');\n }\n try {\n const decoded = Buffer.from(body.content, 'base64').toString('utf-8');\n const parsed = JSON.parse(decoded);\n existingPosts = Array.isArray(parsed.posts) ? parsed.posts : [];\n } catch(e) {\n throw new Error('Failed to decode/parse current posts.json: ' + e.message);\n }\n} else if (status === 404) {\n // Bootstrap empty index \u2014 only valid path for an empty repo.\n existingPosts = [];\n} else {\n throw new Error('Refusing to mutate posts.json \u2014 unexpected status ' + status + ' from contents API: ' + JSON.stringify(env.body || env).slice(0,300));\n}\n\nconst newEntry = buildOut.indexEntry;\nconst merged = [newEntry, ...existingPosts.filter(p => p.slug !== newEntry.slug)];\nconst indexJson = JSON.stringify({ posts: merged }, null, 2);\nconst postJson = JSON.stringify(buildOut.post, null, 2);\n\nreturn [{\n json: {\n headSha: headRef.object.sha,\n treeSha: headCommit.tree.sha,\n slug: buildOut.slug,\n title: buildOut.post.title,\n postPath: 'public/blog/' + buildOut.slug + '.json',\n indexPath: 'public/blog/posts.json',\n postBase64: Buffer.from(postJson).toString('base64'),\n indexBase64: Buffer.from(indexJson).toString('base64'),\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
3120,
0
],
"id": "a000aaaa-0004-0004-0004-000000000004",
"name": "Merge Index + Encode Blobs"
},
{
"parameters": {
"method": "POST",
"url": "https://api.github.com/repos/NJacobs415/manageher-makeover/git/blobs",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "githubApi",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "application/vnd.github+json"
},
{
"name": "X-GitHub-Api-Version",
"value": "2022-11-28"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"content\": \"{{ $json.postBase64 }}\",\n \"encoding\": \"base64\"\n}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
3344,
0
],
"id": "a000aaaa-0005-0005-0005-000000000005",
"name": "GH: Create Post Blob",
"credentials": {
"githubApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"method": "POST",
"url": "https://api.github.com/repos/NJacobs415/manageher-makeover/git/blobs",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "githubApi",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "application/vnd.github+json"
},
{
"name": "X-GitHub-Api-Version",
"value": "2022-11-28"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"content\": \"{{ $node[\"Merge Index + Encode Blobs\"].json.indexBase64 }}\",\n \"encoding\": \"base64\"\n}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
3568,
0
],
"id": "a000aaaa-0006-0006-0006-000000000006",
"name": "GH: Create Index Blob",
"credentials": {
"githubApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"method": "POST",
"url": "https://api.github.com/repos/NJacobs415/manageher-makeover/git/trees",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "githubApi",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "application/vnd.github+json"
},
{
"name": "X-GitHub-Api-Version",
"value": "2022-11-28"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"base_tree\": \"{{ $node[\"Merge Index + Encode Blobs\"].json.treeSha }}\",\n \"tree\": [\n {\n \"path\": \"{{ $node[\"Merge Index + Encode Blobs\"].json.postPath }}\",\n \"mode\": \"100644\",\n \"type\": \"blob\",\n \"sha\": \"{{ $node[\"GH: Create Post Blob\"].json.sha }}\"\n },\n {\n \"path\": \"{{ $node[\"Merge Index + Encode Blobs\"].json.indexPath }}\",\n \"mode\": \"100644\",\n \"type\": \"blob\",\n \"sha\": \"{{ $node[\"GH: Create Index Blob\"].json.sha }}\"\n }\n ]\n}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
3792,
0
],
"id": "a000aaaa-0007-0007-0007-000000000007",
"name": "GH: Create Tree",
"credentials": {
"githubApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"method": "POST",
"url": "https://api.github.com/repos/NJacobs415/manageher-makeover/git/commits",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "githubApi",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "application/vnd.github+json"
},
{
"name": "X-GitHub-Api-Version",
"value": "2022-11-28"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"message\": \"Auto-publish blog: {{ $node[\"Merge Index + Encode Blobs\"].json.title }}\",\n \"tree\": \"{{ $json.sha }}\",\n \"parents\": [\"{{ $node[\"Merge Index + Encode Blobs\"].json.headSha }}\"]\n}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
4016,
0
],
"id": "a000aaaa-0008-0008-0008-000000000008",
"name": "GH: Create Commit",
"credentials": {
"githubApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"method": "PATCH",
"url": "https://api.github.com/repos/NJacobs415/manageher-makeover/git/refs/heads/main",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "githubApi",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "application/vnd.github+json"
},
{
"name": "X-GitHub-Api-Version",
"value": "2022-11-28"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"sha\": \"{{ $json.sha }}\"\n}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
4240,
0
],
"id": "a000aaaa-0009-0009-0009-000000000009",
"name": "GH: Update Ref (atomic)",
"credentials": {
"githubApi": {
"name": "<your credential>"
}
}
}
],
"connections": {
"Every 6 Hours": {
"main": [
[
{
"node": "HTTP Request",
"type": "main",
"index": 0
}
]
]
},
"Check If Blog Exists": {
"main": [
[
{
"node": "Is New Episode?",
"type": "main",
"index": 0
}
]
]
},
"Is New Episode?": {
"main": [
[
{
"node": "Fetch Transcript",
"type": "main",
"index": 0
}
]
]
},
"Clean Transcript": {
"main": [
[
{
"node": "Build Claude Request",
"type": "main",
"index": 0
}
]
]
},
"Claude: Generate Blog Post": {
"main": [
[
{
"node": "Build Blog JSON",
"type": "main",
"index": 0
}
]
]
},
"Build Blog JSON": {
"main": [
[
{
"node": "GH: Get HEAD Ref",
"type": "main",
"index": 0
}
]
]
},
"HTTP Request": {
"main": [
[
{
"node": "Code in JavaScript",
"type": "main",
"index": 0
}
]
]
},
"Code in JavaScript": {
"main": [
[
{
"node": "Check If Blog Exists",
"type": "main",
"index": 0
}
]
]
},
"Build Claude Request": {
"main": [
[
{
"node": "Claude: Generate Blog Post",
"type": "main",
"index": 0
}
]
]
},
"Ping IndexNow": {
"main": [
[
{
"node": "Lookup Guest by Name",
"type": "main",
"index": 0
}
]
]
},
"Lookup Guest by Name": {
"main": [
[
{
"node": "Is Already Sent?",
"type": "main",
"index": 0
}
]
]
},
"Is Already Sent?": {
"main": [
[],
[
{
"node": "Has Existing Contact?",
"type": "main",
"index": 0
}
]
]
},
"Has Existing Contact?": {
"main": [
[
{
"node": "Update Existing Contact",
"type": "main",
"index": 0
}
],
[
{
"node": "Create New Contact",
"type": "main",
"index": 0
}
]
]
},
"Update Existing Contact": {
"main": [
[
{
"node": "Add Tags to Existing",
"type": "main",
"index": 0
}
]
]
},
"Fetch Transcript": {
"main": [
[
{
"node": "Clean Transcript",
"type": "main",
"index": 0
}
]
]
},
"GH: Get HEAD Ref": {
"main": [
[
{
"node": "GH: Get HEAD Commit",
"type": "main",
"index": 0
}
]
]
},
"GH: Get HEAD Commit": {
"main": [
[
{
"node": "GH: Get posts.json (or 404)",
"type": "main",
"index": 0
}
]
]
},
"GH: Get posts.json (or 404)": {
"main": [
[
{
"node": "Merge Index + Encode Blobs",
"type": "main",
"index": 0
}
]
]
},
"Merge Index + Encode Blobs": {
"main": [
[
{
"node": "GH: Create Post Blob",
"type": "main",
"index": 0
}
]
]
},
"GH: Create Post Blob": {
"main": [
[
{
"node": "GH: Create Index Blob",
"type": "main",
"index": 0
}
]
]
},
"GH: Create Index Blob": {
"main": [
[
{
"node": "GH: Create Tree",
"type": "main",
"index": 0
}
]
]
},
"GH: Create Tree": {
"main": [
[
{
"node": "GH: Create Commit",
"type": "main",
"index": 0
}
]
]
},
"GH: Create Commit": {
"main": [
[
{
"node": "GH: Update Ref (atomic)",
"type": "main",
"index": 0
}
]
]
},
"GH: Update Ref (atomic)": {
"main": [
[
{
"node": "Ping IndexNow",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"availableInMCP": true
},
"staticData": {
"node:Every 6 Hours": {
"recurrenceRules": [
18
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
githubApihttpHeaderAuth
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
TMH — Auto Blog Publisher. Uses httpRequest. Scheduled trigger; 27 nodes.
Source: https://github.com/NJacobs415/manageher-makeover/blob/eb51a2af17e2ed5b48c559d5997f5797212912e3/n8n-workflows/At5iovQ74qk4ki5B.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Birthday Automation - Production (Fixed). Uses stopAndError, httpRequest, emailSend, bannerbear. Scheduled trigger; 86 nodes.
This template runs two scheduled workflows to govern Microsoft Entra ID (Azure AD) guest accounts by detecting stale users via Microsoft Graph, staging deletions in SharePoint with a 72-hour window, n
Jira-Allure-Auto-Qa. Uses httpRequest, jira. Scheduled trigger; 68 nodes.
Spotify-Sync-Surrealdb-V1. Uses httpRequest, n8n-nodes-surrealdb, spotify. Scheduled trigger; 62 nodes.
As n8n instances scale, teams often lose track of sub-workflows—who uses them, where they are referenced, and whether they can be safely updated. This leads to inefficiencies like unnecessary copies o