This workflow corresponds to n8n.io template #14973 — we link there as the canonical source.
This workflow follows the Gmail → Google Sheets recipe pattern — see all workflows that pair these two integrations.
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 →
{
"nodes": [
{
"id": "0721f8d0-1413-40ce-a5c4-9cbe6fd5a8c7",
"name": "\ud83d\udccb MAIN \u2014 Workflow Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-128,
-912
],
"parameters": {
"width": 780,
"height": 1028,
"content": "## \ud83c\udf99\ufe0f Product Update Audio Announcements \u2014 Changelog to TTS Audio\n\n**What this workflow does:**\nEvery time a new release is pushed to your GitHub repository (or a Notion changelog page is updated), this workflow:\n1. Fetches the raw changelog/release notes from GitHub Releases API\n2. Pulls the matching structured release notes from a **Notion** database (rich formatted content)\n3. Uses **OpenAI** to rewrite the technical changelog into a natural, podcast-style spoken script\n4. Sends the script to **ElevenLabs TTS API** to generate a professional MP3 audio file\n5. Uploads the MP3 via **UploadToURL** to get a permanent public audio URL\n6. Uses the **HTML node** to compose a beautifully formatted email newsletter with the audio player embed, changelog summary, and a 'Listen Now' CTA button\n7. Sends the newsletter via **Gmail** to your subscriber list (loaded from Google Sheets)\n8. Posts a Slack message to your internal `#product-updates` channel with the audio URL and release summary\n9. **Writes back** to the Notion changelog page \u2014 marks it as 'Audio Published' with the hosted audio URL\n\n**Architecture (unique from all previous templates):**\n- \ud83d\udc19 GitHub Trigger (watches releases \u2014 NEW trigger type)\n- \ud83d\udcd3 Notion \u2014 fetch release notes page (NEW: database query)\n- \ud83e\udd16 OpenAI \u2014 rewrite to spoken script (NEW: different prompt purpose)\n- \ud83c\udf99\ufe0f ElevenLabs TTS API \u2014 audio generation (NEW: TTS node)\n- \u2601\ufe0f UploadToURL \u2014 host the MP3 file (mandatory)\n- \ud83d\udccb Aggregate Node \u2014 bundle subscriber emails (NEW node type)\n- \ud83c\udf10 HTML Node \u2014 compose rich email body (NEW node type)\n- \ud83d\udce7 Gmail \u2014 send newsletter to subscribers (NEW: email delivery)\n- \ud83d\udcac Slack \u2014 post to #product-updates channel (NEW: Slack node)\n- \ud83d\udd04 Notion Update \u2014 write-back audio URL to source page (NEW: bidirectional)\n\n**Setup Requirements:**\n1. GitHub credentials (personal access token with `repo` read scope)\n2. Notion Integration Token \u2014 share your Changelog database with the integration\n3. Notion Database ID for your changelog (from the database page URL)\n4. OpenAI API credentials\n5. ElevenLabs API Key + your preferred Voice ID (from elevenlabs.io/voices)\n6. UploadToURL endpoint configured in upload node\n7. Gmail OAuth2 credentials\n8. Google Sheets with subscriber emails \u2014 sheet named `Subscribers` with column `Email`\n9. Slack Bot Token with `chat:write` scope \u2014 set channel to `#product-updates`\n10. Notion credentials again for the write-back step"
},
"typeVersion": 1
},
{
"id": "6eeaf206-5041-41fd-bd21-2e434d5e9389",
"name": "\ud83d\udcdd Note \u2014 GitHub Trigger, Parse & Notion Fetch",
"type": "n8n-nodes-base.stickyNote",
"position": [
704,
128
],
"parameters": {
"color": 7,
"width": 616,
"height": 616,
"content": "### \ud83d\udc19 Step 1 \u2014 GitHub Release Trigger & Notion Fetch\n**GitHub Trigger:** Fires on every new GitHub Release published to your repo (uses GitHub webhooks \u2014 not polling). Captures tag name, release name, release body (raw markdown changelog), author, and release URL.\n**Code \u2014 Parse Release:** Cleans the raw markdown changelog into plain text. Extracts version number, release date, and categorises changes into `features`, `fixes`, and `improvements` arrays by scanning bullet prefixes.\n**Notion \u2014 Query Database:** Searches your Notion Changelog database for a page matching the version tag. Retrieves the richer formatted description, product area tags, and any manually added context \u2014 supplements the raw GitHub release body."
},
"typeVersion": 1
},
{
"id": "aa818069-eff9-43f7-9f2d-143ba805908c",
"name": "\ud83d\udcdd Note \u2014 Script, TTS & UploadToURL",
"type": "n8n-nodes-base.stickyNote",
"position": [
1328,
112
],
"parameters": {
"color": 7,
"width": 1128,
"height": 632,
"content": "### \ud83c\udf99\ufe0f Step 2 \u2014 Script Writing, TTS Generation & Upload\n**OpenAI \u2014 Spoken Script:** Rewrites the combined GitHub + Notion content into a warm, conversational podcast-style spoken announcement (60-90 seconds when read aloud). Avoids markdown, bullet points, and jargon.\n**ElevenLabs TTS:** Sends the script to ElevenLabs `/v1/text-to-speech/{voice_id}` endpoint. Returns raw MP3 binary audio. Voice, stability, and similarity boost are all configurable.\n**UploadToURL:** Uploads the MP3 binary and returns a permanent public URL \u2014 this URL is used in the email embed, Slack message, and Notion write-back."
},
"typeVersion": 1
},
{
"id": "bb19ab3c-6d68-4732-82fa-ca35a56ddecf",
"name": "\ud83d\udcdd Note \u2014 Subscribers, HTML Build & Gmail Send",
"type": "n8n-nodes-base.stickyNote",
"position": [
2496,
112
],
"parameters": {
"color": 7,
"width": 1032,
"height": 632,
"content": "### \ud83d\udce7 Step 3 \u2014 Subscriber Load, HTML Email & Gmail Send\n**Google Sheets \u2014 Load Subscribers:** Reads all rows from the `Subscribers` sheet. Each row contains at minimum an `Email` field. Returns one item per subscriber.\n**Aggregate Node:** Bundles all subscriber email addresses into a single comma-separated string \u2014 used as the Gmail BCC list so one send covers all subscribers.\n**HTML Node:** Composes a fully formatted HTML email with: audio player embed (using `<audio>` tag), release version header, changelog summary table (features/fixes), and a styled 'Listen Now' CTA button linking to the hosted MP3.\n**Gmail \u2014 Send Newsletter:** Sends the HTML email to all subscribers in BCC. Subject includes version number and release date."
},
"typeVersion": 1
},
{
"id": "70b804a2-49a8-4c31-8805-4985f00b4f0f",
"name": "\ud83d\udcdd Note \u2014 Slack Post & Notion Write-Back",
"type": "n8n-nodes-base.stickyNote",
"position": [
3552,
112
],
"parameters": {
"color": 7,
"width": 520,
"height": 632,
"content": "### \ud83d\udcac Step 4 \u2014 Slack Announcement & Notion Write-Back\n**Slack \u2014 Post to #product-updates:** Posts a rich Slack Block Kit message to your internal channel. Includes: release version, one-line summary, 'Listen to audio update' link (hosted MP3), and a direct link to the GitHub release.\n**Notion \u2014 Update Page:** Writes back to the original Notion changelog page \u2014 sets a `AudioStatus` property to `Published`, stores the `AudioURL` (hosted MP3 link), and stamps the `AudioPublishedAt` timestamp. This prevents duplicate processing if the workflow fires again."
},
"typeVersion": 1
},
{
"id": "760f31b3-035d-4f20-8361-b2ed1ee30618",
"name": "GitHub \u2014 On New Release Published",
"type": "n8n-nodes-base.githubTrigger",
"position": [
752,
480
],
"parameters": {
"owner": "YOUR_GITHUB_ORG_OR_USERNAME",
"events": [
"release"
],
"options": {},
"repository": "YOUR_REPO_NAME"
},
"typeVersion": 1
},
{
"id": "25cd24a1-6e9b-4718-a49c-c2757d2d3237",
"name": "Code \u2014 Parse & Categorise Release Notes",
"type": "n8n-nodes-base.code",
"position": [
976,
480
],
"parameters": {
"jsCode": "const payload = $input.first().json;\n\n// Only process 'published' release events\nconst action = payload.action || '';\nif (action !== 'published') {\n return [];\n}\n\nconst release = payload.release || {};\nconst repo = payload.repository || {};\n\nconst rawBody = release.body || '';\nconst version = release.tag_name || 'v0.0.0';\nconst releaseName = release.name || version;\nconst releaseUrl = release.html_url || '';\nconst author = release.author?.login || 'Team';\nconst publishedAt = release.published_at || new Date().toISOString();\nconst repoName = repo.full_name || repo.name || 'Product';\n\n// Parse changelog markdown into categories\nconst lines = rawBody.split('\\n').map(l => l.trim()).filter(Boolean);\nconst features = [];\nconst fixes = [];\nconst improvements = [];\nconst other = [];\n\nlet currentSection = 'other';\nfor (const line of lines) {\n const lower = line.toLowerCase();\n if (lower.includes('## feat') || lower.includes('### feat') || lower.includes('## new')) { currentSection = 'features'; continue; }\n if (lower.includes('## fix') || lower.includes('### fix') || lower.includes('## bug')) { currentSection = 'fixes'; continue; }\n if (lower.includes('## impr') || lower.includes('### impr') || lower.includes('## change')) { currentSection = 'improvements'; continue; }\n if (line.startsWith('-') || line.startsWith('*') || line.startsWith('\u2022')) {\n const clean = line.replace(/^[-*\u2022]\\s*/, '').trim();\n if (!clean) continue;\n if (currentSection === 'features') features.push(clean);\n else if (currentSection === 'fixes') fixes.push(clean);\n else if (currentSection === 'improvements') improvements.push(clean);\n else other.push(clean);\n }\n}\n\n// Plain text version for TTS script base\nconst plainChangelog = rawBody\n .replace(/#{1,6}\\s*/g, '')\n .replace(/[*_`]/g, '')\n .replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, '$1')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim()\n .substring(0, 2000);\n\nconst releaseDate = new Date(publishedAt).toLocaleDateString('en-US', {\n weekday: 'long', year: 'numeric', month: 'long', day: 'numeric'\n});\n\nreturn [{\n json: {\n version,\n releaseName,\n releaseUrl,\n author,\n publishedAt,\n releaseDate,\n repoName,\n plainChangelog,\n features,\n fixes,\n improvements,\n other,\n totalChanges: features.length + fixes.length + improvements.length + other.length\n }\n}];"
},
"typeVersion": 2
},
{
"id": "ad024d1f-d93e-4896-a479-bbbdfbece7e7",
"name": "Notion \u2014 Query Changelog Database",
"type": "n8n-nodes-base.notion",
"position": [
1184,
480
],
"parameters": {
"limit": 1,
"filters": {
"conditions": [
{
"key": "Version",
"condition": "equals"
}
]
},
"options": {
"downloadFiles": false
},
"resource": "databasePage",
"operation": "getAll",
"databaseId": {
"__rl": true,
"mode": "id",
"value": "YOUR_NOTION_DATABASE_ID"
},
"filterType": "manual"
},
"typeVersion": 2.2
},
{
"id": "91bfee04-63e2-4a57-8fba-8adac177e209",
"name": "Code \u2014 Merge Notion Context + Release Data",
"type": "n8n-nodes-base.code",
"position": [
1408,
480
],
"parameters": {
"jsCode": "const notionResp = $input.first().json;\nconst releaseData = $('Code \u2014 Parse & Categorise Release Notes').item.json;\n\n// Extract richer Notion content if page found\nlet notionSummary = '';\nlet notionPageId = '';\nlet audioAlreadyPublished = false;\n\nif (notionResp && notionResp.id) {\n notionPageId = notionResp.id;\n\n // Extract plain text from Notion title property\n const titleProp = notionResp.properties?.Name?.title ||\n notionResp.properties?.Title?.title || [];\n const titleText = titleProp.map(t => t.plain_text || '').join('');\n\n // Check if audio already published\n const audioStatus = notionResp.properties?.AudioStatus?.select?.name || '';\n if (audioStatus === 'Published') {\n audioAlreadyPublished = true;\n }\n\n // Extract rich text summary\n const summaryProp = notionResp.properties?.Summary?.rich_text || [];\n notionSummary = summaryProp.map(t => t.plain_text || '').join('').trim();\n}\n\nif (audioAlreadyPublished) {\n // Return empty to stop processing\n return [];\n}\n\nreturn [{\n json: {\n ...releaseData,\n notionPageId,\n notionSummary: notionSummary || releaseData.plainChangelog.substring(0, 300)\n }\n}];"
},
"typeVersion": 2
},
{
"id": "cfb52b62-e69f-453a-a905-20f36695ff42",
"name": "OpenAI \u2014 Write Podcast-Style Spoken Script",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
1632,
480
],
"parameters": {
"resource": "chat"
},
"typeVersion": 1.4
},
{
"id": "927bf6f4-a0ed-47ef-ae19-ff7c2591949a",
"name": "ElevenLabs \u2014 Generate MP3 Audio",
"type": "n8n-nodes-base.httpRequest",
"position": [
1856,
480
],
"parameters": {
"url": "=https://api.elevenlabs.io/v1/text-to-speech/YOUR_ELEVENLABS_VOICE_ID",
"method": "POST",
"options": {
"timeout": 60000,
"response": {
"response": {
"responseFormat": "file"
}
}
},
"jsonBody": "={\n \"text\": {{ JSON.stringify($json.choices[0].message.content) }},\n \"model_id\": \"eleven_multilingual_v2\",\n \"voice_settings\": {\n \"stability\": 0.5,\n \"similarity_boost\": 0.85,\n \"style\": 0.2,\n \"use_speaker_boost\": true\n }\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "xi-api-key",
"value": "=YOUR_ELEVENLABS_API_KEY"
},
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "Accept",
"value": "audio/mpeg"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "3521601e-fe5e-4960-a1cd-3db15f417570",
"name": "Code \u2014 Store Script Text Before Upload",
"type": "n8n-nodes-base.code",
"position": [
2064,
480
],
"parameters": {
"jsCode": "// Store the spoken script text for later use before binary overwrites context\nconst aiResp = $('OpenAI \u2014 Write Podcast-Style Spoken Script').item.json;\nconst releaseData = $('Code \u2014 Merge Notion Context + Release Data').item.json;\n\nconst spokenScript = aiResp?.choices?.[0]?.message?.content || '';\n\n// Pass-through: binary data stays in item, we just annotate json\nreturn [{\n json: {\n ...releaseData,\n spokenScript\n },\n binary: $input.first().binary\n}];"
},
"typeVersion": 2
},
{
"id": "c5e59b51-8d2d-4a3e-90c4-6f2efd7b80fc",
"name": "Code \u2014 Store Audio URL + Full Release Data",
"type": "n8n-nodes-base.code",
"position": [
2512,
480
],
"parameters": {
"jsCode": "const uploadResp = $input.first().json;\nconst releaseData = $('Code \u2014 Store Script Text Before Upload').item.json;\n\nconst audioUrl =\n uploadResp?.url ??\n uploadResp?.data?.url ??\n uploadResp?.file?.url ??\n uploadResp?.link ??\n '';\n\nreturn [{\n json: {\n ...releaseData,\n audioUrl\n }\n}];"
},
"typeVersion": 2
},
{
"id": "c63743e0-b669-4e1d-9e75-c48ca0be2e78",
"name": "Google Sheets \u2014 Load Subscriber List",
"type": "n8n-nodes-base.googleSheets",
"position": [
2736,
480
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Subscribers"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "YOUR_GOOGLE_SHEET_ID"
}
},
"typeVersion": 4.5
},
{
"id": "b7b44d9a-fd60-4f29-9baf-ad0e39bc182f",
"name": "Aggregate \u2014 Bundle All Subscriber Emails",
"type": "n8n-nodes-base.aggregate",
"position": [
2944,
480
],
"parameters": {
"options": {},
"aggregate": "aggregateAllItemData",
"destinationFieldName": "subscribers"
},
"typeVersion": 1
},
{
"id": "98a3ca90-bf97-4bed-a957-46aa22aca686",
"name": "Code \u2014 Build HTML Email Newsletter",
"type": "n8n-nodes-base.code",
"position": [
3168,
480
],
"parameters": {
"jsCode": "const aggregated = $input.first().json.subscribers || [];\nconst releaseData = $('Code \u2014 Store Audio URL + Full Release Data').item.json;\n\n// Extract emails into BCC list\nconst emails = aggregated\n .map(row => row.Email || row.email || '')\n .filter(Boolean)\n .join(',');\n\n// Build features/fixes HTML rows\nconst featureRows = (releaseData.features || []).map(f =>\n `<tr><td style=\"padding:6px 0;color:#1a1a2e;\">\u2728 ${f}</td></tr>`\n).join('');\n\nconst fixRows = (releaseData.fixes || []).map(f =>\n `<tr><td style=\"padding:6px 0;color:#1a1a2e;\">\ud83d\udc1b ${f}</td></tr>`\n).join('');\n\nconst improvRows = (releaseData.improvements || []).map(i =>\n `<tr><td style=\"padding:6px 0;color:#1a1a2e;\">\u26a1 ${i}</td></tr>`\n).join('');\n\nconst changeTable = (featureRows || fixRows || improvRows)\n ? `<table style=\"width:100%;border-collapse:collapse;\">${featureRows}${fixRows}${improvRows}</table>`\n : `<p style=\"color:#555;\">${releaseData.plainChangelog.substring(0,300)}...</p>`;\n\nconst htmlBody = `\n<!DOCTYPE html>\n<html>\n<head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"></head>\n<body style=\"margin:0;padding:0;background:#f4f6fb;font-family:Arial,sans-serif;\">\n <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background:#f4f6fb;\">\n <tr><td align=\"center\" style=\"padding:40px 20px;\">\n <table width=\"600\" cellpadding=\"0\" cellspacing=\"0\" style=\"background:#ffffff;border-radius:12px;overflow:hidden;box-shadow:0 2px 12px rgba(0,0,0,0.08);\">\n\n <!-- Header -->\n <tr><td style=\"background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);padding:36px 40px;\">\n <h1 style=\"margin:0;color:#fff;font-size:26px;font-weight:700;\">\ud83c\udf99\ufe0f Product Update</h1>\n <p style=\"margin:8px 0 0;color:rgba(255,255,255,0.85);font-size:15px;\">${releaseData.releaseName} \u00b7 ${releaseData.releaseDate}</p>\n </td></tr>\n\n <!-- Audio Player Section -->\n <tr><td style=\"padding:32px 40px 24px;background:#f9f6ff;text-align:center;\">\n <p style=\"margin:0 0 12px;color:#764ba2;font-size:13px;font-weight:600;text-transform:uppercase;letter-spacing:1px;\">\ud83c\udfa7 Listen to this update</p>\n <audio controls style=\"width:100%;max-width:480px;\">\n <source src=\"${releaseData.audioUrl}\" type=\"audio/mpeg\">\n Your email client doesn't support audio playback.\n </audio>\n <br><br>\n <a href=\"${releaseData.audioUrl}\" style=\"display:inline-block;background:#667eea;color:#fff;text-decoration:none;padding:12px 32px;border-radius:8px;font-size:15px;font-weight:600;\">\u25b6 Listen Now (MP3)</a>\n </td></tr>\n\n <!-- Changelog Summary -->\n <tr><td style=\"padding:32px 40px;\">\n <h2 style=\"margin:0 0 18px;color:#1a1a2e;font-size:19px;\">What's new in ${releaseData.version}</h2>\n ${changeTable}\n </td></tr>\n\n <!-- CTA -->\n <tr><td style=\"padding:0 40px 32px;text-align:center;\">\n <a href=\"${releaseData.releaseUrl}\" style=\"display:inline-block;border:2px solid #667eea;color:#667eea;text-decoration:none;padding:11px 28px;border-radius:8px;font-size:14px;font-weight:600;\">View Full Release Notes on GitHub \u2192</a>\n </td></tr>\n\n <!-- Footer -->\n <tr><td style=\"background:#f4f6fb;padding:20px 40px;text-align:center;border-top:1px solid #eee;\">\n <p style=\"margin:0;color:#999;font-size:12px;\">You're receiving this because you subscribed to ${releaseData.repoName} product updates.</p>\n </td></tr>\n\n </table>\n </td></tr>\n </table>\n</body>\n</html>`;\n\nreturn [{\n json: {\n ...releaseData,\n htmlBody,\n bccList: emails,\n emailSubject: `\ud83c\udf99\ufe0f [${releaseData.version}] ${releaseData.releaseName} \u2014 Listen to the Update`\n }\n}];"
},
"typeVersion": 2
},
{
"id": "91ec0fd2-ba43-4797-b45c-86c059919820",
"name": "Gmail \u2014 Send Audio Newsletter to Subscribers",
"type": "n8n-nodes-base.gmail",
"position": [
3392,
480
],
"parameters": {
"sendTo": "=newsletter@yourdomain.com",
"message": "={{ $json.htmlBody }}",
"options": {
"bccList": "={{ $json.bccList }}",
"replyTo": "user@example.com",
"senderName": "Product Team",
"appendAttribution": false
},
"subject": "={{ $json.emailSubject }}"
},
"typeVersion": 2.1
},
{
"id": "faad1606-961e-4218-93e4-f9300d32e3d5",
"name": "Slack \u2014 Post to #product-updates Channel",
"type": "n8n-nodes-base.slack",
"position": [
3616,
480
],
"parameters": {
"select": "channel",
"channelId": {
"__rl": true,
"mode": "id",
"value": "YOUR_SLACK_CHANNEL_ID",
"cachedResultName": "#product-updates"
},
"otherOptions": {
"unfurl_links": false,
"unfurl_media": false
}
},
"typeVersion": 2.2
},
{
"id": "acda4293-1b81-4985-9dae-6170f6ab5995",
"name": "Notion \u2014 Write Back Audio URL to Changelog Page",
"type": "n8n-nodes-base.notion",
"position": [
3824,
480
],
"parameters": {
"operation": "update"
},
"typeVersion": 2.2
},
{
"id": "7d211080-0815-419b-8a29-8f49a9d5c64e",
"name": "Upload a File",
"type": "n8n-nodes-uploadtourl.uploadToUrl",
"position": [
2272,
480
],
"parameters": {},
"credentials": {
"uploadToUrlApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
}
],
"connections": {
"Upload a File": {
"main": [
[
{
"node": "Code \u2014 Store Audio URL + Full Release Data",
"type": "main",
"index": 0
}
]
]
},
"ElevenLabs \u2014 Generate MP3 Audio": {
"main": [
[
{
"node": "Code \u2014 Store Script Text Before Upload",
"type": "main",
"index": 0
}
]
]
},
"GitHub \u2014 On New Release Published": {
"main": [
[
{
"node": "Code \u2014 Parse & Categorise Release Notes",
"type": "main",
"index": 0
}
]
]
},
"Notion \u2014 Query Changelog Database": {
"main": [
[
{
"node": "Code \u2014 Merge Notion Context + Release Data",
"type": "main",
"index": 0
}
]
]
},
"Code \u2014 Build HTML Email Newsletter": {
"main": [
[
{
"node": "Gmail \u2014 Send Audio Newsletter to Subscribers",
"type": "main",
"index": 0
}
]
]
},
"Google Sheets \u2014 Load Subscriber List": {
"main": [
[
{
"node": "Aggregate \u2014 Bundle All Subscriber Emails",
"type": "main",
"index": 0
}
]
]
},
"Code \u2014 Store Script Text Before Upload": {
"main": [
[
{
"node": "Upload a File",
"type": "main",
"index": 0
}
]
]
},
"Code \u2014 Parse & Categorise Release Notes": {
"main": [
[
{
"node": "Notion \u2014 Query Changelog Database",
"type": "main",
"index": 0
}
]
]
},
"Aggregate \u2014 Bundle All Subscriber Emails": {
"main": [
[
{
"node": "Code \u2014 Build HTML Email Newsletter",
"type": "main",
"index": 0
}
]
]
},
"Slack \u2014 Post to #product-updates Channel": {
"main": [
[
{
"node": "Notion \u2014 Write Back Audio URL to Changelog Page",
"type": "main",
"index": 0
}
]
]
},
"Code \u2014 Merge Notion Context + Release Data": {
"main": [
[
{
"node": "OpenAI \u2014 Write Podcast-Style Spoken Script",
"type": "main",
"index": 0
}
]
]
},
"Code \u2014 Store Audio URL + Full Release Data": {
"main": [
[
{
"node": "Google Sheets \u2014 Load Subscriber List",
"type": "main",
"index": 0
}
]
]
},
"OpenAI \u2014 Write Podcast-Style Spoken Script": {
"main": [
[
{
"node": "ElevenLabs \u2014 Generate MP3 Audio",
"type": "main",
"index": 0
}
]
]
},
"Gmail \u2014 Send Audio Newsletter to Subscribers": {
"main": [
[
{
"node": "Slack \u2014 Post to #product-updates Channel",
"type": "main",
"index": 0
}
]
]
}
}
}
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.
uploadToUrlApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Deliver your product updates in a modern, accessible format. This workflow automatically transforms GitHub releases into podcast-style audio announcements and distributes them via email and Slack.
Source: https://n8n.io/workflows/14973/ — 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.
Stop chasing blurry receipts and manually typing expense data. This workflow creates an intelligent, "snap-and-submit" reimbursement pipeline that hosts photos via UploadToURL, extracts deep data via
This template is ideal for HR teams, startup founders, operations leads, remote-first companies, and freelancers managing onboarding manually or across multiple tools.
n8n Recruitment. Uses gmailTrigger, openAi, googleSheets, gmail. Event-driven trigger; 20 nodes.
Build a Phone Agent to qualify outbound leads and inbound calls with RetellAI -vide. Uses stickyNote, googleSheetsTrigger, twilio, httpRequest. Event-driven trigger; 18 nodes.
This workflow is a comprehensive automation engine that bridges the gap between raw client data and expert-level financial advice. Upon receiving a new onboarding form from Google Sheets, the system f