Pressemeldung-zu-Audio-Vorlage v3
Dies ist ein Content Creation, Multimodal AI-Bereich Automatisierungsworkflow mit 23 Nodes. Hauptsächlich werden If, Code, Gmail, HttpRequest, GmailTrigger und andere Nodes verwendet. Konvertieren Sie Kurznachrichten in KI-Podcasts mit GPT-4o Mini und ElevenLabs
- •Google-Konto + Gmail API-Anmeldedaten
- •Möglicherweise sind Ziel-API-Anmeldedaten erforderlich
- •OpenAI API Key
Verwendete Nodes (23)
Kategorie
{
"id": "kmIE2vOxAQCnkYZl",
"meta": {
"instanceId": "e3b8c25a8cb5935de24fc3d0e60a61032f6dc1d69388fb7fdac79d48279775d0",
"templateCredsSetupCompleted": true
},
"name": "Newsletter to Audio Template v3",
"tags": [],
"nodes": [
{
"id": "efdedc12-d60f-441d-ba3a-5e331f3bb776",
"name": "Haftnotiz",
"type": "n8n-nodes-base.stickyNote",
"position": [
-640,
-96
],
"parameters": {
"width": 432,
"height": 1120,
"content": "## 🎧 Newsletter-to-Audio Conversation Flow\n\nThis workflow turns unread newsletters from your email inbox into dynamic audio conversations between two AI voices — inspired by **Google's NotebookLM** ability to summarize and humanize dense content.\n\n### ✨ How it works:\n1. 📨 **Fetch Unread Newsletters** using Gmail (e.g., sender: \"your_favorite@newsletter.com\").\n2. 🧠 **Summarize and Reformat** the content into a dialogue using an LLM (like OpenAI or Gemini).\n3. 🗣️ **Generate Voices** for each part of the conversation using a TTS service (e.g., ElevenLabs, Google TTS).\n4. 🎛️ **Merge Audio Segments** into a natural back-and-forth flow using FFmpeg or audio nodes.\n5. 📤 **Send the Final Audio** file back to your email inbox (or deliver to another channel like Telegram or Drive).\n\n### 💡 Inspired by:\nNotebookLM's approach to making long-form text more digestible by turning it into personalized, conversational summaries.\n\nYou can modify:\n- 🎙️ The tone and voice of the personas\n- 🕵️ Email filters (e.g., subject or sender)\n- 📫 The delivery method (email, storage, etc.)\n\n> Ideal for turning passive subscriptions into engaging, hands-free content.\n\n---\n\n### 📬 Need help or want to collaborate?\n\nIf you have any questions, need help setting this up, or want to share feedback — feel free to reach out: \n📩 **Luis.acosta@news2podcast.com**\n\nIf you're looking to build something more advanced with audio and AI — such as automatically updating podcasts to Spotify or other audio platforms — let me know and I’ll figure out how I can help you!\n"
},
"typeVersion": 1
},
{
"id": "0d7b28ee-2163-4e5f-b552-1e85174a1a00",
"name": "Newsletter abrufen",
"type": "n8n-nodes-base.gmailTrigger",
"position": [
0,
0
],
"parameters": {
"simple": false,
"filters": {
"q": "from:demandcurve.com"
},
"options": {},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
}
},
"credentials": {
"gmailOAuth2": {
"id": "kk1pIRVZu8RMHadC",
"name": "Gmail account"
}
},
"typeVersion": 1.2
},
{
"id": "37149829-5c91-4654-9173-e7c2617f0db3",
"name": "Haftnotiz1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-160,
-96
],
"parameters": {
"color": 7,
"width": 432,
"height": 1104,
"content": "## 📨 Step 1: Get Newsletter Content (Gmail or Webhook)\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nThis is the entry point of the workflow.\n\nBy default, we use the **Gmail node** to fetch unread newsletter emails from your inbox — perfect for personal automation. You can filter by sender with search \"from:\" (like Substack, Medium, or Beehiiv) to capture only relevant messages.\n\n### 🔁 Alternative: Webhook for Product Integration\n\nIf you're integrating this workflow into a larger product or service, you can **swap this Gmail node for a Webhook node** to receive newsletter or any other content via API from your app or users.\n\nThis makes it easy to embed the experience in platforms where users submit newsletter URLs, HTML, or pasted content manually.\n\n### ✅ Expected Output\n\n- `Body (HTML or plain text)`: Main content to be summarized and converted into a dialogue.\n"
},
"typeVersion": 1
},
{
"id": "54688146-cc4f-4499-9596-9fa11dac491e",
"name": "Haftnotiz2",
"type": "n8n-nodes-base.stickyNote",
"position": [
320,
-96
],
"parameters": {
"color": 7,
"width": 496,
"height": 1104,
"content": "## 🧠 Step 2: Generate Dialogue Script\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nThis node uses the **OpenAI Chat model (GPT-4o Mini)** to convert newsletter content into a natural, spoken-style conversation between two AI personas: `men1` and `men2`.\n\n### 🎯 Purpose\nTurn dense, static newsletter content into an engaging, human-like dialogue — similar to how **NotebookLM** rephrases documents as friendly, flowing discussions.\n\n### 🧑🤝🧑 Voice Personalities\n- `voice1`: Curious, expressive, informal — brings humor, reacts with emotion, and keeps it conversational.\n- `voice2`: Calm, reflective, slightly ironic — adds context, simplifies, and balances the tone.\n\n### 🧩 Prompt Logic\nThe custom prompt:\n- Sets a clear structure: intro, content breakdown, and closing\n- Encourages a spoken, spontaneous tone (not read-from-a-script)\n- Uses `<break time=\"1.5s\" />` tags to simulate realistic pauses\n- Demands **at least 10,000 characters** (~10 minutes of audio)\n- Output is formatted only as:\n - voice1: …\n - voice2: …\n\n### 📥 Input Source\n- Pulls the newsletter content from the Gmail node:\n`{{$('Get Newsletter').first().json.text}}`\n\n### ✅ Model Choice: GPT-4o Mini\nWe use **GPT-4o Mini** for its **excellent cost/performance ratio**, enabling fast generation of long-form dialogue at scale.\n\n> This is the core creative step of the workflow — translating static information into an immersive audio script, ready for voice synthesis.\n\n"
},
"typeVersion": 1
},
{
"id": "d1cf8808-1af6-46d1-88c7-40cacd361cc1",
"name": "Dialogskript generieren",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
416,
0
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini",
"cachedResultName": "GPT-4O-MINI"
},
"options": {},
"messages": {
"values": [
{
"content": "=You are the scriptwriter of a podcast that transforms dense written content into a lively, natural conversation between two AI speakers, `voice1` and `voice2`.\n\nYour task is to turn the following newsletter content into a **realistic audio dialogue**. The conversation should be fluid, informal, and engaging — similar in tone and structure to how NotebookLM rewrites long documents as discussions. It must sound like two well-informed people exchanging ideas, not like a text being read aloud.\n\n### Roles\n\n- **voice1**: Curious, expressive, casual, often injects humor or everyday references. Tends to ask questions, react with surprise or amusement, and bring lightness to the discussion.\n- **voice2**: Analytical, composed, insightful. Adds perspective, context, and a slightly ironic or dry sense of humor. Offers clarity without sounding robotic.\n\nUse realistic, human-like phrasing with brief interjections (`\"Right?\"`, `\"Let me stop you there\"`, `\"That's exactly it\"`). Use `<break time=\"1.5s\" />` tags occasionally to simulate natural pauses.\n\n### Structure\n\n1. **Introduction**: Set the scene naturally. Briefly introduce what the episode is about based on the content, without listing or labeling sections. Present `voice1` and `voice2` through dialogue, not narration.\n2. **Content Breakdown**: For each key idea or section from the newsletter:\n - Paraphrase the content in spoken language.\n - Embed the headline or theme organically in the conversation.\n - Include personal reactions, examples, and small tangents to make it relatable.\n - Open loops by teasing questions or ideas that will be answered later in the conversation.\n - Maintain curiosity and variety in tone and rhythm.\n3. **Closing**: End warmly and casually, with a brief comment on what stood out or what’s coming next (no need for formal farewells).\n\n### Requirements\n\n- The script must be at least **ten thousand characters** (about 15 minutes of speech).\n- Use **commas** to separate items in a list, not periods.\n- Format the output as a single uninterrupted block of text with clear speaker tags:\n \nvoice1: …\nvoice2: …\n\nYou will be given a newsletter input under this key:\n\n{{$('Get Newsletter').first().json.text}}\n\nGenerate only the final dialogue script — no explanations, bullet points, or headings. Just the conversation in English.\n\n\n"
}
]
}
},
"credentials": {
"openAiApi": {
"id": "qRfKea43wEoiDJoP",
"name": "OpenAi account"
}
},
"typeVersion": 1.8
},
{
"id": "3254c9ca-c0f3-4ca2-bfb0-da126df9970c",
"name": "Skript aufteilen",
"type": "n8n-nodes-base.code",
"position": [
1024,
0
],
"parameters": {
"jsCode": "/**\n * This Function node takes the script from the previous node\n * and splits it using \"voice1:\" and \"voice2:\" as delimiters.\n * Each resulting segment retains the respective identifier.\n */\n\nconst script = $input.first().json.message.content || \"\";\n\n// Ensure consistent line breaks\nconst normalizedScript = script.replace(/\\r\\n/g, \"\\n\");\n\n// Split the script while keeping \"voice1:\" and \"voice2:\" in the result\nconst segments = normalizedScript.split(/(?=(?:voice1:|voice2:))/g).map(s => s.trim()).filter(Boolean);\n\n// Return one item per segment\nreturn segments.map(segment => {\n return {\n json: {\n segment\n }\n };\n});"
},
"typeVersion": 2
},
{
"id": "c0953943-c607-456f-aeab-09544e40ba60",
"name": "Haftnotiz3",
"type": "n8n-nodes-base.stickyNote",
"position": [
848,
-96
],
"parameters": {
"color": 7,
"width": 496,
"height": 1104,
"content": "## ✂️ Step 3: Split Script into Speaker Segments\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nThis Function node takes the full dialogue script generated by OpenAI and **splits it into individual segments** based on speaker turns (`men1:` and `men2:`).\n\n### 🔧 What it does:\n- Reads the script from the previous node (`message.content`)\n- Normalizes line breaks (`\\r\\n` → `\\n`) for consistency\n- Uses a regex to split the text while **retaining speaker labels**\n- Filters out empty results\n- Returns each intervention as a separate item with:\n ```json\n {\n \"segment\": \"men1: …\" \n }\n\n### 📌 Why this is important\n\nEach segment will later be passed to the TTS system as a **standalone voice generation request**, allowing different voices to be applied for `men1` and `men2`.\n\nThis step transforms a long script into a list of **atomic, voice-ready dialogue chunks**.\n\n"
},
"typeVersion": 1
},
{
"id": "121a604c-2d64-4c02-9801-7ec18c78dbaf",
"name": "Über Elemente iterieren",
"type": "n8n-nodes-base.splitInBatches",
"position": [
1488,
0
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "d2ad4b04-d346-4f79-998b-c73b9008024a",
"name": "Wenn",
"type": "n8n-nodes-base.if",
"position": [
1920,
96
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "93609a28-55f5-439e-8238-a48375255f4f",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $json.cleanedText }}",
"rightValue": "voice1:"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "b7a6be7e-3d9d-4eb9-9e26-a9ed1e8a84f5",
"name": "Haftnotiz4",
"type": "n8n-nodes-base.stickyNote",
"position": [
1392,
-96
],
"parameters": {
"color": 7,
"width": 1184,
"height": 2000,
"content": "## 🔁 Step 4–6: Loop Through Segments & Generate Voices with ElevenLabs\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nThis part of the workflow loops through each dialogue segment and sends it to **ElevenLabs** to generate realistic voice audio based on the speaker (`men1` or `men2`).\n\n\n### 🙌 Support this Template\n\nIf you'd like to support my work and help me continue building free, high-quality templates for the n8n community, **you can use my affiliate link when signing up for ElevenLabs**. \n👉 It **doesn’t cost you anything extra**, and helps keep this project alive. \n**[Click here to support via ElevenLabs](https://try.elevenlabs.io/ds0cvdfiufax)**\n\n\n### 🔧 How it works\n\n1. **Function Node – Clean Segment** \n - Removes problematic characters like quotation marks and line breaks\n - Stores the cleaned result as `cleanedText`\n\n2. **IF Node – Detect Speaker** \n - Checks if `cleanedText` starts with `\"voice1:\"` \n - Branches into `voice1` or `voice2` to assign the correct voice\n\n3. **Function Node – Prepare Text for TTS** \n - Strips the `\"voice1:\"` or `\"voice2:\"` label using regex \n - Outputs a clean `modifiedString` to be sent to ElevenLabs\n\n4. **HTTP Request Node – ElevenLabs TTS** \n - Uses a **custom auth header**:\n ```json\n {\n \"headers\": {\n \"xi-api-key\": \"YOUR_API_KEY_FOR_ELEVENLABS\"\n }\n }\n ```\n - Endpoint: \n ```\n https://api.elevenlabs.io/v1/text-to-speech/YOUR_VOICE_ID\n ```\n Replace `YOUR_VOICE_ID` with the voice you want to use from your ElevenLabs dashboard. After logging in, go to Voices, find the voice that best fits your needs, and copy the Voice ID by clicking on the three dots [...]\n\n - Sample JSON body:\n ```json\n {\n \"text\": \"Your cleaned and formatted dialogue text here.\",\n \"model_id\": \"eleven_multilingual_v2\",\n \"voice_settings\": {\n \"stability\": 0.4,\n \"similarity_boost\": 0.75\n }\n }\n ```\n\n - You can have one node for each speaker (`voice1`, `voice2`) with a different **voice ID** in the URL.\n\n---\n\n### 🔁 Output\n- Each iteration sends one segment to ElevenLabs.\n- The result will be a **binary audio file** (MP3) for each speaker’s line.\n- You can later merge them using FFmpeg or the Audio Merge node.\n\n---\n\n> This section is key to giving your newsletter content an actual voice — literally! And with ElevenLabs' high-quality synthesis and GPT-4o Mini's conversational structure, the result is natural, polished audio narration.\n"
},
"typeVersion": 1
},
{
"id": "e9926fad-2ec7-429b-9cbc-e13e537fd2f9",
"name": "Function Node – Segment bereinigen",
"type": "n8n-nodes-base.code",
"position": [
1744,
96
],
"parameters": {
"jsCode": "const paragraph = $input.first().json.segment; \nif (!paragraph) {\n throw new Error(\"No se encontró contenido de texto en el correo.\");\n}\n\nlet cleanedText = paragraph\n .replace(/\"/g, \"\")\n .replace(/“/g, \"\")\n .replace(/”/g, \"\");\n\ncleanedText = cleanedText.replace(/\\n/g, \"\");\n\nconsole.log(\"Texto limpio sin comillas ni saltos de línea:\", cleanedText);\n\nreturn [{ json: { cleanedText } }];\n"
},
"typeVersion": 2
},
{
"id": "2cab07f6-fb5c-4242-a0e9-702256e8708d",
"name": "Function Node – Text für TTS vorbereiten",
"type": "n8n-nodes-base.code",
"position": [
2128,
160
],
"parameters": {
"jsCode": "const cleanedText = $input.first().json.cleanedText;\n\nif (typeof cleanedText !== \"string\") {\n throw new Error(\"cleanedText debe ser un string.\");\n}\n\nconsole.log(\"Texto original:\", JSON.stringify(cleanedText, null, 2));\n\nif (cleanedText.includes(\"men1:\")) {\n console.log(\"✅ 'voice1:' detectado en el texto original.\");\n} else {\n console.log(\"❌ 'voice1:' NO encontrado en el texto original. ¡Revisar input!\");\n}\n\n\nconst modifiedString = cleanedText.replace(/\\bvoice1:\\s*/gi, \"\").trim();\n\n\nconsole.log(\"Texto modificado:\", JSON.stringify(modifiedString, null, 2));\n\n\nif (modifiedString.includes(\"voice1:\")) {\n console.log(\"❌ 'voice1:' sigue presente en el texto modificado. ¡El regex debe ajustarse!\");\n} else {\n console.log(\"✅ 'voice1:' eliminado correctamente.\");\n}\n\n\nreturn [\n {\n json: {\n modifiedString\n }\n }\n];"
},
"typeVersion": 2
},
{
"id": "8b891a0d-9a6a-4b16-a459-02cf8f153c0a",
"name": "Function Node – Text für TTS1 vorbereiten",
"type": "n8n-nodes-base.code",
"position": [
2128,
368
],
"parameters": {
"jsCode": "const cleanedText = $input.first().json.cleanedText;\n\nif (typeof cleanedText !== \"string\") {\n throw new Error(\"cleanedText debe ser un string.\");\n}\n\nconsole.log(\"Texto original:\", JSON.stringify(cleanedText, null, 2));\n\nif (cleanedText.includes(\"voice2:\")) {\n console.log(\"✅ 'voice2:' detectado en el texto original.\");\n} else {\n console.log(\"❌ 'voice2:' NO encontrado en el texto original. ¡Revisar input!\");\n}\n\nconst modifiedString = cleanedText.replace(/\\bvoice2:\\s*/gi, \"\").trim();\n\nconsole.log(\"Texto modificado:\", JSON.stringify(modifiedString, null, 2));\n\nif (modifiedString.includes(\"voice2:\")) {\n console.log(\"❌ 'voice2:' sigue presente en el texto modificado. ¡El regex debe ajustarse!\");\n} else {\n console.log(\"✅ 'voice2:' eliminado correctamente.\");\n}\n\nreturn [\n {\n json: {\n modifiedString\n }\n }\n];"
},
"typeVersion": 2
},
{
"id": "e12b7916-44b5-4ed8-8b99-1485e8ecf36d",
"name": "Function Node – Text für TTS – Stimme 1 vorbereiten",
"type": "n8n-nodes-base.httpRequest",
"position": [
2384,
160
],
"parameters": {
"url": "=https://api.elevenlabs.io/v1/text-to-speech/uYXf8XasLslADfZ2MB4u",
"method": "POST",
"options": {},
"jsonBody": "={\n \"text\": \"{{ $json.modifiedString }}\",\n \"model_id\": \"eleven_multilingual_v2\",\n \"voice_settings\": {\n \"stability\": 0.5,\n \"similarity_boost\": 0.75\n }\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpCustomAuth",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"credentials": {
"httpCustomAuth": {
"id": "zPsZAr8WingF0NsY",
"name": "Elevenlabs"
}
},
"retryOnFail": true,
"typeVersion": 4.2
},
{
"id": "9894a491-669b-493e-bd29-00281fe79150",
"name": "Function Node – Text für TTS – Stimme 2 vorbereiten",
"type": "n8n-nodes-base.httpRequest",
"position": [
2384,
368
],
"parameters": {
"url": "=https://api.elevenlabs.io/v1/text-to-speech/UgBBYS2sOqTuMpoF3BR0",
"method": "POST",
"options": {},
"jsonBody": "={\n \"text\": \"{{ $json.modifiedString }}\",\n \"model_id\": \"eleven_multilingual_v2\",\n \"voice_settings\": {\n \"stability\": 0.5,\n \"similarity_boost\": 0.75\n }\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpCustomAuth",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"credentials": {
"httpCustomAuth": {
"id": "zPsZAr8WingF0NsY",
"name": "Elevenlabs"
}
},
"retryOnFail": true,
"typeVersion": 4.2
},
{
"id": "d46fbc80-a9ea-439a-84fc-e665f63170f4",
"name": "Audio-Chunks speichern",
"type": "n8n-nodes-base.readWriteFile",
"position": [
2384,
-16
],
"parameters": {
"options": {},
"fileName": "=/newsletter2podcast/tmp/audio_{{$itemIndex}}.mp3",
"operation": "write"
},
"typeVersion": 1
},
{
"id": "4b7569cb-94d1-4a4a-9a24-4f1223c47be3",
"name": "Haftnotiz5",
"type": "n8n-nodes-base.stickyNote",
"position": [
2608,
-96
],
"parameters": {
"color": 7,
"width": 672,
"height": 1792,
"content": "## 🎼 Step 7–8: Prepare FFmpeg List and Merge Audio\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nThis section merges all the individual voice segments into a single MP3 using FFmpeg, following the standard concat workflow via a `.txt` list file.\n\n⚠️ **To use this step, you must have FFmpeg installed and run n8n in a local or self-hosted environment.** This will not work on n8n Cloud or any environment that does not allow executing system commands.\n\n\n### 🧾 Step 7 – Generate `concat_list.txt`\n\nA **Code node** performs the following:\n\n1. Loops through all items, assuming each item has:\n - `fileName`\n - `directory` (only for the first file)\n2. Builds a properly formatted FFmpeg list:\n ```\n file 'audio_1.mp3'\n file 'audio_2.mp3'\n file 'audio_3.mp3'\n ```\n3. Converts the result to **Base64-encoded binary data** so it can be saved using the `Write Binary File` node.\n\n📝 The result is written to:\n\n```\n/newsletter2podcast/tmp/concat_list.txt\n```\n\nCode logic highlights:\n- First item uses full path: `${directory}/${fileName}`\n- Others use just `fileName`\n- Converts the list to a `Buffer` and then to base64 for n8n binary handling\n\n---\n\n### 🎬 Step 8 – Merge Audio with FFmpeg\n\nAn `Execute Command` node runs:\n\n```bash\nffmpeg -y -f concat -safe 0 -i /newsletter2podcast/tmp/concat_list.txt -c copy /newsletter2podcast/tmp/final_merged.mp3\n```\n\nThis generates the final, merged audio file using **lossless concatenation**:\n\n- `-y`: Overwrites existing output\n- `-f concat`: Uses FFmpeg’s concat demuxer\n- `-safe 0`: Allows absolute paths\n- `-c copy`: Copies streams without re-encoding\n\n🆕 Output location:\n\n```\n/newsletter2podcast/tmp/final_merged.mp3\n```\n\nDelete the audio chunks and the concat_list\n\n```bash\nfind /newsletter2podcast/tmp/ -type f ! -name \"final_merged.mp3\" -delete\n```\n---\n\n### 📌 Notes\n\n- Ensure all audio files are encoded consistently (same format, codec, bitrate)\n- If ElevenLabs was used, they’re already compatible\n- If sync issues occur, you can preprocess files with re-encoding (`-c:a libmp3lame`)\n- This process assumes FFmpeg is installed and accessible in your n8n environment\n\n---\n\n> ✅ This step finalizes your audio transformation — from dialogue chunks to a smooth, podcast-ready episode.\n"
},
"typeVersion": 1
},
{
"id": "244a5ef9-14bd-4005-bba9-c224fea02c65",
"name": "`concat_list.txt` generieren",
"type": "n8n-nodes-base.code",
"position": [
2704,
-16
],
"parameters": {
"jsCode": "/**\n * This Code node will:\n * 1. Gather all file paths from the incoming items (assuming each item has `item.json.filePath`).\n * 2. Build a single text string, each line in FFmpeg concat format: `file '/path/to/audio.mp3'`\n * 3. Convert that text to binary (Base64) so the next node (\"Write Binary File\") can save it as `concat_list.txt`.\n */\n\nconst items = $input.all();\n\n// Build the concat list\nlet concatListText = '';\n\nitems.forEach((item, index) => {\n let filePath;\n\n\n // Use only fileName for the rest\n filePath = item.json.fileName;\n\n\n if (filePath) {\n concatListText += `file '${filePath}'\\n`;\n }\n});\n\n// Convert the text to a Buffer, then to Base64\nconst buffer = Buffer.from(concatListText, 'utf-8');\nconst base64Data = buffer.toString('base64');\n\n// Return a single item containing the binary data\nreturn [\n {\n json: {},\n binary: {\n data: {\n data: base64Data,\n mimeType: 'text/plain',\n fileName: 'concat_list.txt'\n }\n }\n }\n];"
},
"typeVersion": 2
},
{
"id": "a53ecd13-10fb-4d5b-9149-f82447804f07",
"name": "concat_list speichern",
"type": "n8n-nodes-base.readWriteFile",
"position": [
2912,
-16
],
"parameters": {
"options": {},
"fileName": "/newsletter2podcast/tmp/concat_list.txt",
"operation": "write"
},
"typeVersion": 1
},
{
"id": "1259c775-2f7e-48be-b71a-8ace50bcf378",
"name": "Audio-Chunks zusammenführen und Dateien löschen",
"type": "n8n-nodes-base.executeCommand",
"position": [
3120,
-16
],
"parameters": {
"command": "ffmpeg -y -f concat -safe 0 -i /newsletter2podcast/tmp/concat_list.txt -c copy /newsletter2podcast/tmp/final_merged.mp3\n\nfind /newsletter2podcast/tmp/ -type f ! -name \"final_merged.mp3\" -delete\n"
},
"typeVersion": 1
},
{
"id": "5d5a1452-74be-40b4-901a-bf746581651a",
"name": "final_merged lesen",
"type": "n8n-nodes-base.readWriteFile",
"position": [
3424,
-16
],
"parameters": {
"options": {},
"fileSelector": "/newsletter2podcast/tmp/final_merged.mp3"
},
"typeVersion": 1
},
{
"id": "c1caeef4-199e-43ad-a4ed-e95bbb1b2385",
"name": "Audio senden",
"type": "n8n-nodes-base.gmail",
"position": [
3632,
-16
],
"webhookId": "a01c59d4-d3d5-4aab-90fd-e87978735d23",
"parameters": {
"sendTo": "={{$('Get Newsletter').first().json.to.text}}",
"message": "=<h1>Hello! Here your newsletter in Audio Version </h1>\n",
"options": {
"attachmentsUi": {
"attachmentsBinary": [
{}
]
}
},
"subject": "=[Audio Version] {{$('Get Newsletter').first().json.subject}}"
},
"credentials": {
"gmailOAuth2": {
"id": "kk1pIRVZu8RMHadC",
"name": "Gmail account"
}
},
"typeVersion": 2.1
},
{
"id": "b27f0c70-2c69-4338-aa9f-3a0fad785d2c",
"name": "Haftnotiz6",
"type": "n8n-nodes-base.stickyNote",
"position": [
3312,
-96
],
"parameters": {
"color": 7,
"width": 560,
"height": 1184,
"content": "## ✉️ Step 9: Read Merged Audio & Send via Email\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nThis final section completes the workflow by:\n\n1. **Reading the final merged MP3** using a `Read Binary File` node\n2. **Sending it as an email attachment** with a `Gmail` node\n\n---\n\n### 📂 Read Binary File\n\n- Path: \n/newsletter2podcast/tmp/final_merged.mp3\n\n---\n\n### 📤 Gmail Node – Send Audio\n\nUse the `Gmail` node to:\n\n- Set the **recipient email** dynamically or statically\n- Add a subject and message body \n- Attach the binary file:\n- `Binary Property`: `data`\n- Attachment filename: `newsletter_audio.mp3`\n\n---\n\n### 📝 Notes\n\n- You can personalize the subject with dynamic values (like the original newsletter subject).\n- Make sure Gmail authentication is correctly configured in your credentials.\n- You may add logic before this node to notify users via Telegram, Slack, or cloud storage.\n\n---\n\n> ✅ This final step completes the journey from unread newsletter to hands-free audio experience, right in your inbox.\n"
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "61e1c74c-8506-4907-a433-56b50ff33151",
"connections": {
"d2ad4b04-d346-4f79-998b-c73b9008024a": {
"main": [
[
{
"node": "2cab07f6-fb5c-4242-a0e9-702256e8708d",
"type": "main",
"index": 0
}
],
[
{
"node": "8b891a0d-9a6a-4b16-a459-02cf8f153c0a",
"type": "main",
"index": 0
}
]
]
},
"c1caeef4-199e-43ad-a4ed-e95bbb1b2385": {
"main": [
[]
]
},
"3254c9ca-c0f3-4ca2-bfb0-da126df9970c": {
"main": [
[
{
"node": "121a604c-2d64-4c02-9801-7ec18c78dbaf",
"type": "main",
"index": 0
}
]
]
},
"0d7b28ee-2163-4e5f-b552-1e85174a1a00": {
"main": [
[
{
"node": "d1cf8808-1af6-46d1-88c7-40cacd361cc1",
"type": "main",
"index": 0
}
]
]
},
"121a604c-2d64-4c02-9801-7ec18c78dbaf": {
"main": [
[
{
"node": "d46fbc80-a9ea-439a-84fc-e665f63170f4",
"type": "main",
"index": 0
}
],
[
{
"node": "e9926fad-2ec7-429b-9cbc-e13e537fd2f9",
"type": "main",
"index": 0
}
]
]
},
"a53ecd13-10fb-4d5b-9149-f82447804f07": {
"main": [
[
{
"node": "1259c775-2f7e-48be-b71a-8ace50bcf378",
"type": "main",
"index": 0
}
]
]
},
"d46fbc80-a9ea-439a-84fc-e665f63170f4": {
"main": [
[
{
"node": "244a5ef9-14bd-4005-bba9-c224fea02c65",
"type": "main",
"index": 0
}
]
]
},
"5d5a1452-74be-40b4-901a-bf746581651a": {
"main": [
[
{
"node": "c1caeef4-199e-43ad-a4ed-e95bbb1b2385",
"type": "main",
"index": 0
}
]
]
},
"d1cf8808-1af6-46d1-88c7-40cacd361cc1": {
"main": [
[
{
"node": "3254c9ca-c0f3-4ca2-bfb0-da126df9970c",
"type": "main",
"index": 0
}
]
]
},
"244a5ef9-14bd-4005-bba9-c224fea02c65": {
"main": [
[
{
"node": "a53ecd13-10fb-4d5b-9149-f82447804f07",
"type": "main",
"index": 0
}
]
]
},
"e9926fad-2ec7-429b-9cbc-e13e537fd2f9": {
"main": [
[
{
"node": "d2ad4b04-d346-4f79-998b-c73b9008024a",
"type": "main",
"index": 0
}
]
]
},
"2cab07f6-fb5c-4242-a0e9-702256e8708d": {
"main": [
[
{
"node": "e12b7916-44b5-4ed8-8b99-1485e8ecf36d",
"type": "main",
"index": 0
}
]
]
},
"1259c775-2f7e-48be-b71a-8ace50bcf378": {
"main": [
[
{
"node": "5d5a1452-74be-40b4-901a-bf746581651a",
"type": "main",
"index": 0
}
]
]
},
"8b891a0d-9a6a-4b16-a459-02cf8f153c0a": {
"main": [
[
{
"node": "9894a491-669b-493e-bd29-00281fe79150",
"type": "main",
"index": 0
}
]
]
},
"e12b7916-44b5-4ed8-8b99-1485e8ecf36d": {
"main": [
[
{
"node": "121a604c-2d64-4c02-9801-7ec18c78dbaf",
"type": "main",
"index": 0
}
]
]
},
"9894a491-669b-493e-bd29-00281fe79150": {
"main": [
[
{
"node": "121a604c-2d64-4c02-9801-7ec18c78dbaf",
"type": "main",
"index": 0
}
]
]
}
}
}Wie verwende ich diesen Workflow?
Kopieren Sie den obigen JSON-Code, erstellen Sie einen neuen Workflow in Ihrer n8n-Instanz und wählen Sie "Aus JSON importieren". Fügen Sie die Konfiguration ein und passen Sie die Anmeldedaten nach Bedarf an.
Für welche Szenarien ist dieser Workflow geeignet?
Experte - Content-Erstellung, Multimodales KI
Ist es kostenpflichtig?
Dieser Workflow ist völlig kostenlos. Beachten Sie jedoch, dass Drittanbieterdienste (wie OpenAI API), die im Workflow verwendet werden, möglicherweise kostenpflichtig sind.
Verwandte Workflows
Luis Acosta
@podcast-toolsI'm passionate about people and technology, with a hands-on and creative approach. Lately, my main focus has shifted towards process automation using artificial intelligence and the creation of meaningful content. I enjoy exploring how emerging technologies can simplify workflows, enhance productivity, and open new creative possibilities.
Diesen Workflow teilen