This workflow corresponds to n8n.io template #10870 — we link there as the canonical source.
This workflow follows the Form Trigger → Gmail 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": "64ef094b-c447-4dc3-a3c3-8b0d777b5759",
"name": "GET Form",
"type": "n8n-nodes-base.formTrigger",
"position": [
-2080,
624
],
"parameters": {
"options": {
"path": "audio-transcription",
"ignoreBots": false,
"respondWithOptions": {
"values": {
"formSubmittedText": "Your file has been received; an email will be sent to you upon completion of transcription or in case of error."
}
}
},
"formTitle": "Audio Transcription",
"formFields": {
"values": [
{
"fieldType": "file",
"fieldLabel": "file",
"requiredField": true,
"acceptFileTypes": ".mp3"
},
{
"fieldType": "email",
"fieldLabel": "email",
"requiredField": true
}
]
},
"formDescription": "Select an audio file to transcribe and an email address to receive the result."
},
"typeVersion": 2.3
},
{
"id": "7787660b-343e-49a0-8177-c77e263002fe",
"name": "Make 4MiB Chunks",
"type": "n8n-nodes-base.code",
"position": [
-1632,
624
],
"parameters": {
"jsCode": "// 4 MiB chunks (FileFlows UI)\nconst CHUNK_SIZE = $(\"Configuration\").first().json.chunk_size;\n\nconst item = items[0];\n\n// Get the file uploaded by the Form Trigger\n// (the field is named \"file\" \u2192 binary.file; fallback to the first binary found)\nconst bin =\n item.binary?.file ||\n (item.binary ? item.binary[Object.keys(item.binary)[0]] : null);\n\nif (!bin?.data) {\n throw new Error('No file received. Make sure the field is named \"file\".');\n}\n\nconst override =\n item.json?.fields?.fileNameOverride?.toString().trim() ||\n item.json?.fileNameOverride?.toString().trim() ||\n '';\n\nconst fileName = override || bin.fileName || 'upload.bin';\nconst mimeType = bin.mimeType || 'application/octet-stream';\n\nconst buf = Buffer.from(bin.data, 'base64');\nconst totalChunks = Math.max(1, Math.ceil(buf.length / CHUNK_SIZE));\n\nconst out = [];\nfor (let i = 0; i < totalChunks; i++) {\n const start = i * CHUNK_SIZE;\n const end = Math.min(start + CHUNK_SIZE, buf.length);\n const slice = buf.subarray(start, end);\n\n out.push({\n json: {\n fileName,\n chunkNumber: i, // 0-based like the UI\n totalChunks,\n size: slice.length\n },\n binary: {\n chunk: {\n data: slice.toString('base64'),\n fileName,\n mimeType\n }\n }\n });\n}\n\nreturn out;\n"
},
"typeVersion": 2
},
{
"id": "fc76d2f5-8ea6-4d8e-bf64-7772d1735860",
"name": "Upload Chunk",
"type": "n8n-nodes-base.httpRequest",
"position": [
-960,
688
],
"parameters": {
"url": "={{ $('Configuration').item.json.fileflows_url }}/api/library-file/upload",
"method": "POST",
"options": {},
"sendBody": true,
"contentType": "multipart-form-data",
"bodyParameters": {
"parameters": [
{
"name": "fileName",
"value": "={{$json[\"fileName\"]}}"
},
{
"name": "chunkNumber",
"value": "={{$json[\"chunkNumber\"]}}"
},
{
"name": "totalChunks",
"value": "={{$json[\"totalChunks\"]}}"
},
{
"name": "file",
"parameterType": "formBinaryData",
"inputDataFieldName": "chunk"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "d0277bd8-7c34-4220-9005-4f1aa00c2409",
"name": "Result",
"type": "n8n-nodes-base.noOp",
"position": [
-1184,
384
],
"parameters": {},
"typeVersion": 1
},
{
"id": "646cd48f-d686-4d8a-a477-f892c2bfbf4d",
"name": "If succeed",
"type": "n8n-nodes-base.if",
"position": [
-736,
384
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "7cb6c8ab-074b-4ccf-a1e1-cc0163c9090d",
"operator": {
"type": "string",
"operation": "exists",
"singleValue": true
},
"leftValue": "={{ $json.data }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "9c135f4b-a927-4ed0-97c0-7e634be06668",
"name": "Split audio file",
"type": "n8n-nodes-base.httpRequest",
"position": [
-512,
240
],
"parameters": {
"url": "={{ $('Configuration').item.json.fileflows_url }}/api/library-file/manually-add",
"method": "POST",
"options": {},
"jsonBody": "={\n \"FlowUid\": \"{{ $('Configuration').first().json.flowUid }}\",\n \"Files\": [\n \"{{ $json.data }}\"\n ],\n \"CustomVariables\": {\n \"callbackUrl\": \"{{$execution.resumeUrl}}\"\n }\n} ",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "accept",
"value": "*/*"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "035b6116-13ae-4077-b74c-f1f0f6ee5909",
"name": "Wait",
"type": "n8n-nodes-base.wait",
"position": [
-288,
240
],
"parameters": {
"resume": "webhook",
"options": {},
"httpMethod": "POST",
"resumeUnit": "minutes",
"resumeAmount": 30,
"limitWaitTime": true
},
"typeVersion": 1.1
},
{
"id": "fbfdf35a-f759-40c0-ace6-5b92ad89dc51",
"name": "Split Audio",
"type": "n8n-nodes-base.code",
"position": [
-64,
240
],
"parameters": {
"jsCode": "const binaries = $input.first().binary;\nconst entries = Object.entries(binaries);\n\nconst Audio = [];\nfor (let index = 0; index < entries.length; index++) {\n const [key, value] = entries[index];\n\n Audio.push({\n json: {\n fileExtension: value.fileExtension,\n fileName: value.fileName,\n fileSize: value.fileSize,\n fileType: value.fileType,\n mimeType: value.mimeType,\n },\n binary: {\n [\"Audio\"]: value, // Preserve the complete binary structure\n },\n });\n}\n\n// \u26a0\ufe0f Important : send back array of items\nreturn Audio;"
},
"typeVersion": 2
},
{
"id": "138f793f-72eb-4d6b-a91e-56d26986a4ec",
"name": "Loop Over Segments",
"type": "n8n-nodes-base.splitInBatches",
"position": [
160,
240
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "2bfd6a91-ba2d-4be5-b728-5f0fb4138fea",
"name": "OpenAI",
"type": "@n8n/n8n-nodes-langchain.openAi",
"onError": "continueErrorOutput",
"position": [
608,
336
],
"parameters": {
"options": {
"language": "fr"
},
"resource": "audio",
"operation": "transcribe",
"binaryPropertyName": "Audio"
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 1.8,
"waitBetweenTries": 5000
},
{
"id": "fb77ddde-81bb-4d33-ad6c-18feb4554337",
"name": "Result transcription",
"type": "n8n-nodes-base.noOp",
"position": [
384,
144
],
"parameters": {},
"typeVersion": 1
},
{
"id": "4e2d9d43-db6e-400e-b7c3-e6b9d30c28bd",
"name": "Loop Over Chunks",
"type": "n8n-nodes-base.splitInBatches",
"position": [
-1408,
624
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "3bdb454b-e54e-41da-a98d-5cf2c4049830",
"name": "Merge transcription",
"type": "n8n-nodes-base.code",
"position": [
608,
144
],
"parameters": {
"jsCode": "let res = \"\";\n// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n res += item.json.text;\n}\n\nreturn {\n transcription: res\n};"
},
"typeVersion": 2
},
{
"id": "587d7489-863d-459b-bbc6-c902f0745bae",
"name": "Convert to File",
"type": "n8n-nodes-base.convertToFile",
"position": [
832,
144
],
"parameters": {
"options": {
"encoding": "utf8",
"fileName": "transcription.txt"
},
"operation": "toText",
"sourceProperty": "transcription"
},
"typeVersion": 1.1
},
{
"id": "ce3854cc-d870-4a47-b931-676bdf3b2181",
"name": "Configuration",
"type": "n8n-nodes-base.set",
"position": [
-1856,
624
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "cfb1a46f-20fa-4a40-b52e-da8249cbd31b",
"name": "chunk_size",
"type": "number",
"value": "={{ 4 * 1024 * 1024 }}"
},
{
"id": "eff0f114-6e49-43ef-aae0-a078dd671833",
"name": "fileflows_url",
"type": "string",
"value": "http://0.0.0.0:5000"
},
{
"id": "dbbed56f-1ca8-4106-aed8-14f7d6009068",
"name": "flowUid",
"type": "string",
"value": "65fad732-0ac3-4f6d-ac10-2d31ef84c154"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "290bec95-3807-4d7f-abbc-affdeb54553e",
"name": "Filter temporary files",
"type": "n8n-nodes-base.filter",
"position": [
-960,
384
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "46105005-e7f3-4fd9-9d2d-491a4bab372e",
"operator": {
"type": "string",
"operation": "notEndsWith"
},
"leftValue": "={{ $json.data }}",
"rightValue": ".temp"
}
]
}
},
"typeVersion": 2.2,
"alwaysOutputData": true
},
{
"id": "114c1df2-99b8-49bc-86c5-3404d27f6718",
"name": "Rate Limit Delay",
"type": "n8n-nodes-base.wait",
"position": [
832,
400
],
"parameters": {},
"typeVersion": 1.1
},
{
"id": "cfebb196-a52b-4594-8725-51236e0e4e22",
"name": "Send Email with Transcription",
"type": "n8n-nodes-base.gmail",
"position": [
1056,
144
],
"parameters": {
"sendTo": "={{ $('GET Form').first().json.email }}",
"message": "Hi,\n\nYour audio transcription is complete and attached to this email.\n\nBest regards",
"options": {
"attachmentsUi": {
"attachmentsBinary": [
{}
]
}
},
"subject": "Your transcription is ready"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "9b1de36f-6b8f-4044-9fa2-6dd8c641a51b",
"name": "Send Error",
"type": "n8n-nodes-base.gmail",
"position": [
848,
608
],
"parameters": {
"sendTo": "={{ $('GET Form').first().json.email }}",
"message": "Hi,\n\nWe encountered an issue with the translation model.\nPlease retry in a moment.\n\nBest",
"options": {},
"subject": "Your transcription encountered an issue"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "e1e89a89-165d-4aa1-b15a-d5aff0d0fc4d",
"name": "Send Error1",
"type": "n8n-nodes-base.gmail",
"position": [
-512,
480
],
"parameters": {
"sendTo": "={{ $('GET Form').first().json.email }}",
"message": "Hi,\n\nWe encountered an issue to split your file.\nPlease retry in a moment.\n\nBest",
"options": {},
"subject": "Your transcription encountered an issue"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "55862df0-b4b5-4078-b90b-998c893710b9",
"name": "Chunk",
"type": "n8n-nodes-base.noOp",
"position": [
-1184,
576
],
"parameters": {},
"typeVersion": 1
},
{
"id": "d911c170-5767-49e7-8959-53cf809a7f4d",
"name": "Segment",
"type": "n8n-nodes-base.noOp",
"position": [
384,
336
],
"parameters": {},
"typeVersion": 1
},
{
"id": "2aee5e34-e10d-4149-9dfc-ea05a2d5b4a8",
"name": "Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2848,
-96
],
"parameters": {
"width": 676,
"height": 1128,
"content": "## Long-Form Audio Transcription\n\n**Problem:** OpenAI Whisper API has a 25 MB file size limit (~20 minutes of audio)\n\n**Solution:** This workflow overcomes the limitation by:\n1. Splitting audio into 15-minute segments (FileFlows + FFmpeg)\n2. Transcribing each segment individually (OpenAI Whisper)\n3. Merging all transcriptions into one file\n4. Emailing the complete result\n\n**Workflow Stages:**\n- **Stage 1:** Upload & Chunk (green area below)\n- **Stage 2:** Audio Splitting (blue area)\n- **Stage 3:** Transcription (orange area)\n- **Stage 4:** Delivery (purple area)\n\n**Use Cases:** Meetings, conferences, podcasts, interviews (hours-long recordings)\n\n**Documentation:** https://github.com/JulienDelRio/My-Interesting-n8n-Workflows/tree/main/Full%20audio%20transcription%20with%20FileFlows%20and%20OpenAI\n\n**Workflow for FileFlows** can be found the workflow here : https://github.com/JulienDelRio/My-Interesting-n8n-Workflows/blob/main/Full%20audio%20transcription%20with%20FileFlows%20and%20OpenAI/FileFlows%20-%20Split%20audio%20for%20n8n.json\n\n\n### \u2699\ufe0f Configuration Required\n\n**Before activation, configure:**\n\n1. **FileFlows Connection:**\n - Update FileFlows URL in Configuration node\n - Set correct Flow UID from FileFlows\n - Ensure FileFlows is accessible from n8n\n\n2. **OpenAI Credentials:**\n - Add OpenAI API key in credentials\n - Assign to OpenAI node\n\n3. **Gmail Credentials:**\n - Setup Gmail OAuth2 credentials\n - Assign to all email nodes\n\n4. **Language (Optional):**\n - Default: French (fr)\n - Change in OpenAI node parameters\n - Or remove for auto-detection\n\n**FileFlows Setup:**\n- Import FileFlows workflow\n- Install FFmpeg\n- Configure /media/segments/ storage"
},
"typeVersion": 1
},
{
"id": "d0b1f7d2-8819-4ec9-9d33-183051233741",
"name": "Stage 4 Delivery",
"type": "n8n-nodes-base.stickyNote",
"position": [
352,
-32
],
"parameters": {
"color": 7,
"width": 876,
"height": 132,
"content": "## STEP 4: Delivery\n\nPackage transcription and email to user"
},
"typeVersion": 1
},
{
"id": "cc22e417-d574-4098-b4f1-50c4690f3bfe",
"name": "Stage 3 Transcription",
"type": "n8n-nodes-base.stickyNote",
"position": [
176,
784
],
"parameters": {
"color": 7,
"width": 800,
"height": 152,
"content": "## STEP 3: OpenAI Transcription\n\nTranscribe each 15-minute segment using OpenAI Whisper API"
},
"typeVersion": 1
},
{
"id": "5441f26c-c5df-4464-b794-e45c3199ddb5",
"name": "Stage 2 Splitting",
"type": "n8n-nodes-base.stickyNote",
"position": [
-624,
64
],
"parameters": {
"color": 7,
"width": 720,
"height": 140,
"content": "## STEP 2: Audio Splitting with FileFlows\n\nSplit audio into 15-minute segments to stay under OpenAI's 25 MB limit."
},
"typeVersion": 1
},
{
"id": "a0c0ed38-3f9a-4237-9012-a24a03e05c3d",
"name": "Stage 1 Upload",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2112,
912
],
"parameters": {
"color": 7,
"width": 1280,
"height": 134,
"content": "## \ud83d\udce4 STEP 1: Upload & Chunk to FileFlows\n\nUpload large audio files to FileFlows in manageable chunks"
},
"typeVersion": 1
}
],
"connections": {
"Wait": {
"main": [
[
{
"node": "Split Audio",
"type": "main",
"index": 0
}
]
]
},
"Chunk": {
"main": [
[
{
"node": "Upload Chunk",
"type": "main",
"index": 0
}
]
]
},
"OpenAI": {
"main": [
[
{
"node": "Rate Limit Delay",
"type": "main",
"index": 0
}
],
[
{
"node": "Send Error",
"type": "main",
"index": 0
}
]
]
},
"Result": {
"main": [
[
{
"node": "Filter temporary files",
"type": "main",
"index": 0
}
]
]
},
"Segment": {
"main": [
[
{
"node": "OpenAI",
"type": "main",
"index": 0
}
]
]
},
"GET Form": {
"main": [
[
{
"node": "Configuration",
"type": "main",
"index": 0
}
]
]
},
"If succeed": {
"main": [
[
{
"node": "Split audio file",
"type": "main",
"index": 0
}
],
[
{
"node": "Send Error1",
"type": "main",
"index": 0
}
]
]
},
"Split Audio": {
"main": [
[
{
"node": "Loop Over Segments",
"type": "main",
"index": 0
}
]
]
},
"Upload Chunk": {
"main": [
[
{
"node": "Loop Over Chunks",
"type": "main",
"index": 0
}
]
]
},
"Configuration": {
"main": [
[
{
"node": "Make 4MiB Chunks",
"type": "main",
"index": 0
}
]
]
},
"Convert to File": {
"main": [
[
{
"node": "Send Email with Transcription",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Chunks": {
"main": [
[
{
"node": "Result",
"type": "main",
"index": 0
}
],
[
{
"node": "Chunk",
"type": "main",
"index": 0
}
]
]
},
"Make 4MiB Chunks": {
"main": [
[
{
"node": "Loop Over Chunks",
"type": "main",
"index": 0
}
]
]
},
"Rate Limit Delay": {
"main": [
[
{
"node": "Loop Over Segments",
"type": "main",
"index": 0
}
]
]
},
"Split audio file": {
"main": [
[
{
"node": "Wait",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Segments": {
"main": [
[
{
"node": "Result transcription",
"type": "main",
"index": 0
}
],
[
{
"node": "Segment",
"type": "main",
"index": 0
}
]
]
},
"Merge transcription": {
"main": [
[
{
"node": "Convert to File",
"type": "main",
"index": 0
}
]
]
},
"Result transcription": {
"main": [
[
{
"node": "Merge transcription",
"type": "main",
"index": 0
}
]
]
},
"Filter temporary files": {
"main": [
[
{
"node": "If succeed",
"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.
gmailOAuth2openAiApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This template is designed for content creators, podcasters, businesses, and researchers who need to transcribe long audio recordings that exceed OpenAI Whisper's 25 MB file size limit (~20 minutes of audio).
Source: https://n8n.io/workflows/10870/ — 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.
What it is An automated LinkedIn content system that takes a simple form (idea + optional file), generates LinkedIn posts with OpenAI, stores them in Notion, builds Google Slides carousels, and auto-p
Automatically gather hundreds of real customer reviews from five major platforms in one run using Thordata API and Proxy — Trustpilot, Capterra, Chrome Web Store, TrustRadius, and Product Hunt — then
This workflow enables seamless speech-to-text transcription, AI-powered summarization, sentiment analysis, and automated email delivery. It supports two different input modes: Form Upload (Local File)
This workflow automates weather forecast delivery by collecting city names, fetching 5-day forecasts from OpenWeatherMap, and generating professionally formatted HTML emails using GPT-4. The AI create
This workflow automates the "speed-to-lead" process for insurance agencies. It instantly triggers an AI voice call when a new lead comes in, qualifies their needs via conversation, and automatically g