This workflow corresponds to n8n.io template #16427 — we link there as the canonical source.
This workflow follows the Gmail → Google Drive 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 →
{
"id": "iZz4JefE5d45kAuw",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "AI-Powered Multilingual Lab Report Translator for Patients",
"tags": [],
"nodes": [
{
"id": "0aff9bf6-7566-40e7-b3ab-f46b7e384776",
"name": "Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2512,
-720
],
"parameters": {
"width": 688,
"height": 540,
"content": "## \ud83c\udfe5 AI Lab Report Summariser & Translator\n\n### How it works\nWhen a new PDF is uploaded to a watched Google Drive folder, this workflow downloads it, extracts the text, and uses GPT-4o-mini to parse structured data \u2014 patient name, report type, findings, and detected language. It then produces a plain-English explanation of every test result (no jargon), translates that into the patient's preferred language, and emails the final summary as a formatted Markdown attachment via Gmail.\n\nError alerts at three key AI steps fire to Slack so issues never go unnoticed.\n\n### Setup steps\n1. **Google Drive** \u2014 Connect OAuth2 and update the `folderToWatch` ID to your own lab reports folder.\n2. **OpenAI** \u2014 Add your API key. All three AI nodes use `gpt-4o-mini` by default.\n3. **Gmail** \u2014 Connect OAuth2 and update the `sendTo` field to use a dynamic patient email expression instead of the hardcoded placeholder.\n4. **Slack** \u2014 Connect OAuth2 and set the correct channel ID in all three error alert nodes.\n5. **Test** \u2014 Upload a sample PDF to your Drive folder and run the workflow. Check the Gmail output and Slack for any errors.\n\n> \u26a0\ufe0f This workflow processes medical documents. Ensure your Drive folder, Gmail account, and any storage comply with your organisation's data privacy policy."
},
"typeVersion": 1
},
{
"id": "ede5b689-08fa-469c-a911-e90899d06808",
"name": "Section: File Ingestion",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1616,
-272
],
"parameters": {
"color": 7,
"width": 620,
"height": 760,
"content": "## \ud83d\udcc2 File Ingestion\n\nPolls a Google Drive folder every minute for new PDF uploads. When one appears, it downloads the file and extracts all raw text \u2014 ready to pass into the AI pipeline."
},
"typeVersion": 1
},
{
"id": "48de0ca9-f14d-4f0c-b606-7abc01923b6a",
"name": "Section: AI Extraction & Parsing",
"type": "n8n-nodes-base.stickyNote",
"position": [
-976,
-240
],
"parameters": {
"color": 7,
"width": 592,
"height": 732,
"content": "## \ud83e\udde0 AI Extraction & Parsing\n\nGPT-4o-mini reads the raw PDF text and returns structured JSON \u2014 patient details, report type, lab name, test findings, and the detected language. A robust code node handles malformed responses and strips markdown fences before parsing."
},
"typeVersion": 1
},
{
"id": "18da7b21-5f96-4833-8993-559cf4b99789",
"name": "Section: Plain-Language Summary",
"type": "n8n-nodes-base.stickyNote",
"position": [
-352,
-320
],
"parameters": {
"color": 7,
"width": 592,
"height": 930,
"content": "## \ud83d\udcac Plain-Language Summary\n\nConverts clinical findings into a warm, jargon-free explanation any patient can understand. Each result gets a status (Normal / Borderline / Abnormal) and an everyday analogy. Output is clean Markdown with a 'What to do next' section."
},
"typeVersion": 1
},
{
"id": "07444db6-96c7-4f6c-9f78-300568826f41",
"name": "Section: Translation & Report Assembly",
"type": "n8n-nodes-base.stickyNote",
"position": [
272,
-368
],
"parameters": {
"color": 7,
"width": 528,
"height": 1042,
"content": "## \ud83c\udf0d Translation & Report Assembly\n\nIf the patient's preferred language differs from English, GPT-4o-mini translates the full summary while preserving Markdown formatting. A code node then assembles the final report with header metadata and converts it to a downloadable `.md` file."
},
"typeVersion": 1
},
{
"id": "c92661b4-9de2-4876-b3a8-fb05f52749b4",
"name": "Section: Patient Email Delivery",
"type": "n8n-nodes-base.stickyNote",
"position": [
848,
-304
],
"parameters": {
"color": 7,
"width": 440,
"height": 824,
"content": "## \ud83d\udce7 Patient Email Delivery\n\nSends the formatted HTML email with the Markdown summary attached. The email references the patient's name, report type, and date. Update the `sendTo` field to use `{{ $json.patient_email }}` once your data reliably contains patient emails."
},
"typeVersion": 1
},
{
"id": "a184350b-8b04-4924-9d14-7af8138807c2",
"name": "Credentials & Security",
"type": "n8n-nodes-base.stickyNote",
"position": [
1408,
384
],
"parameters": {
"color": 3,
"width": 320,
"height": 256,
"content": "## \ud83d\udd10 Credentials & Security\n\nUse OAuth2 for Google Drive, Gmail, and Slack. Use API key auth for OpenAI. Never hardcode personal emails or folder IDs \u2014 replace with environment variables or n8n credential manager. Restrict Drive folder access to authorised staff only."
},
"typeVersion": 1
},
{
"id": "c4a6a1f6-e4b2-42dc-969a-ed7e85c9233b",
"name": "Google Drive: Watch for New Lab Report",
"type": "n8n-nodes-base.googleDriveTrigger",
"position": [
-1600,
64
],
"parameters": {
"event": "fileCreated",
"options": {},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
},
"triggerOn": "specificFolder",
"folderToWatch": {
"__rl": true,
"mode": "list",
"value": "YOUR_GOOGLE_DRIVE_FOLDER_ID",
"cachedResultUrl": "https://drive.google.com/drive/folders/YOUR_GOOGLE_DRIVE_FOLDER_ID",
"cachedResultName": "Lab Reports Folder"
}
},
"credentials": {
"googleDriveOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "25581f38-aa1a-4b70-bc2c-60619ed4ed9a",
"name": "Google Drive: Download PDF",
"type": "n8n-nodes-base.googleDrive",
"position": [
-1344,
64
],
"parameters": {
"fileId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.id }}"
},
"options": {},
"operation": "download"
},
"credentials": {
"googleDriveOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 3
},
{
"id": "37483d18-481b-45fa-85ad-ba5de0941c80",
"name": "Extract Text: Parse PDF to Raw Text",
"type": "n8n-nodes-base.extractFromFile",
"position": [
-1120,
64
],
"parameters": {
"options": {},
"operation": "pdf"
},
"typeVersion": 1
},
{
"id": "dfc5d212-d6f7-46d0-b8bd-17a3ef3500e3",
"name": "AI: Extract Structure + Detect Language",
"type": "@n8n/n8n-nodes-langchain.openAi",
"onError": "continueErrorOutput",
"position": [
-912,
64
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini",
"cachedResultName": "GPT-4O-MINI"
},
"options": {
"temperature": 0.1
},
"messages": {
"values": [
{
"role": "system",
"content": "You are a medical document analyst. Your job is to extract structured data from raw lab report text and detect the patient's language and preferred language if stated.\n\nFrom the raw text, extract and return ONLY a JSON object (no markdown, no explanation):\n{\n \"patient_name\": \"full name or Unknown\",\n \"patient_email\": \"email or Unknown\",\n \"report_date\": \"date or Unknown\",\n \"report_type\": \"e.g. Blood Test, MRI, Urine Analysis\",\n \"lab_name\": \"lab or hospital name or Unknown\",\n \"detected_language\": \"language of the report e.g. English, Hindi, French, German\",\n \"preferred_language\": \"patient preferred language if mentioned, else same as detected_language\",\n \"raw_findings\": \"all test results, values, reference ranges as-is from the report\"\n}"
},
{
"content": "=Extract structured data from this lab report:\n\n{{ $json.text }}"
}
]
}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.4
},
{
"id": "4be0b1f5-b916-4931-abfd-65b8bfebf09b",
"name": "Code: Parse & Sanitise Extraction JSON",
"type": "n8n-nodes-base.code",
"position": [
-512,
48
],
"parameters": {
"jsCode": "// Get raw response from GPT\nlet raw = $('AI: Extract Structure + Detect Language').item.json.message.content;\n\n// Step 1: Strip markdown code fences if GPT wrapped in ```json ... ```\nraw = raw.replace(/^```json\\s*/i, '').replace(/^```\\s*/i, '').replace(/```\\s*$/i, '').trim();\n\n// Step 2: Sanitize bad control characters inside JSON string values\nraw = raw\n .replace(/\\t/g, ' ')\n .replace(/\\r\\n/g, ' ')\n .replace(/\\r/g, ' ')\n .replace(/\\n/g, ' ')\n .replace(/[\\x00-\\x1F\\x7F]/g, ' ');\n\n// Step 3: Try to parse cleanly\nlet parsed;\ntry {\n parsed = JSON.parse(raw);\n} catch(e) {\n const match = raw.match(/\\{[\\s\\S]*\\}/);\n if (match) {\n try {\n parsed = JSON.parse(match[0]);\n } catch(e2) {\n const extractField = (field) => {\n const re = new RegExp('\"' + field + '\"\\\\s*:\\\\s*\"([^\"]*?\")');\n const m = raw.match(re);\n return m ? m[1] : 'Unknown';\n };\n parsed = {\n patient_name: extractField('patient_name'),\n patient_email: extractField('patient_email'),\n report_date: extractField('report_date'),\n report_type: extractField('report_type'),\n lab_name: extractField('lab_name'),\n detected_language: extractField('detected_language'),\n preferred_language: extractField('preferred_language'),\n raw_findings: extractField('raw_findings')\n };\n }\n } else {\n throw new Error('Could not extract JSON from GPT response: ' + raw.substring(0, 200));\n }\n}\n\nconst pdfText = $('Extract Text: Parse PDF to Raw Text').item.json.text || '';\nconst fileName = $('Google Drive: Watch for New Lab Report').item.json.name || 'lab_report.pdf';\nconst fileId = $('Google Drive: Watch for New Lab Report').item.json.id;\n\nreturn [{ json: {\n ...parsed,\n pdf_raw_text: pdfText,\n file_name: fileName,\n file_id: fileId\n}}];"
},
"typeVersion": 2
},
{
"id": "d32a4fda-95b9-4433-9a37-4840b3a0eeda",
"name": "AI: Simplify Findings to Plain English",
"type": "@n8n/n8n-nodes-langchain.openAi",
"onError": "continueErrorOutput",
"position": [
-272,
48
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini",
"cachedResultName": "GPT-4O-MINI"
},
"options": {
"temperature": 0.4
},
"messages": {
"values": [
{
"role": "system",
"content": "You are a compassionate medical communication expert. Your job is to convert raw lab report findings into a clear, patient-friendly plain-language summary in ENGLISH only \u2014 even if the original report is in another language.\n\nRules:\n- Never use medical jargon without explaining it in simple terms\n- Use analogies where helpful (e.g. 'Your haemoglobin is like the oxygen-carrying capacity of your blood')\n- For each finding, clearly state: what was tested, the result, whether it is Normal / Borderline / Abnormal, and what it means in everyday life\n- End with a friendly 'What to do next' section\n- Format output as clean Markdown\n- Be warm and reassuring in tone"
},
{
"content": "=Patient: {{ $json.patient_name }}\nReport Type: {{ $json.report_type }}\nReport Date: {{ $json.report_date }}\nLab: {{ $json.lab_name }}\n\nRaw Findings:\n{{ $json.raw_findings }}\n\nPlease write a plain-language summary in English."
}
]
}
},
"typeVersion": 1.4
},
{
"id": "e2f08c89-2c38-4b4f-8291-31c09640d944",
"name": "Code: Attach Plain English Summary",
"type": "n8n-nodes-base.code",
"position": [
112,
48
],
"parameters": {
"jsCode": "// Carry forward all fields + plain English summary\nconst structuredData = $('Code: Parse & Sanitise Extraction JSON').item.json;\nconst plainEnglish = $('AI: Simplify Findings to Plain English').item.json.message.content;\n\nreturn [{ json: {\n ...structuredData,\n plain_english_summary: plainEnglish\n}}];"
},
"typeVersion": 2
},
{
"id": "41b31c01-eeb3-4f27-992c-aae4ec981bca",
"name": "AI: Translate Summary to Patient Language",
"type": "@n8n/n8n-nodes-langchain.openAi",
"onError": "continueErrorOutput",
"position": [
352,
48
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini",
"cachedResultName": "GPT-4O-MINI"
},
"options": {
"temperature": 0.2
},
"messages": {
"values": [
{
"role": "system",
"content": "=You are a professional medical translator. Translate the following plain-language medical summary accurately into {{ $json.preferred_language }}.\n\nRules:\n- Preserve all Markdown formatting (headings, bold, bullet points)\n- Keep medical terms in the target language (use local equivalents where they exist)\n- Keep the warm, reassuring tone\n- If the preferred language is English, return the text unchanged\n- Output ONLY the translated Markdown \u2014 no explanation, no preamble"
},
{
"content": "=Translate this medical summary to {{ $json.preferred_language }}:\n\n{{ $json.plain_english_summary }}"
}
]
}
},
"typeVersion": 1.4
},
{
"id": "27cb534c-f958-4871-9bf9-aeaa67da2ee7",
"name": "Code: Build Final Markdown Report",
"type": "n8n-nodes-base.code",
"position": [
688,
48
],
"parameters": {
"jsCode": "const structuredData = $('Code: Attach Plain English Summary').item.json;\nconst translatedSummary = $('AI: Translate Summary to Patient Language').item.json.message.content;\nconst now = new Date().toISOString();\n\nconst finalMarkdown = `# \ud83c\udfe5 Lab Report Summary\\n\\n**Patient:** ${structuredData.patient_name}\\n**Report Type:** ${structuredData.report_type}\\n**Report Date:** ${structuredData.report_date}\\n**Lab:** ${structuredData.lab_name}\\n**Language:** ${structuredData.preferred_language}\\n**Processed:** ${now}\\n\\n---\\n\\n${translatedSummary}`;\n\nreturn [{ json: {\n ...structuredData,\n translated_summary: translatedSummary,\n final_markdown: finalMarkdown,\n processed_at: now\n}}];"
},
"typeVersion": 2
},
{
"id": "f08e1a18-40c8-47d8-8496-0f9e156973a6",
"name": "Code: Convert Report to Markdown File",
"type": "n8n-nodes-base.code",
"position": [
896,
48
],
"parameters": {
"jsCode": "const markdownText = $('Code: Build Final Markdown Report').item.json.final_markdown;\n\nconst binaryData = await this.helpers.prepareBinaryData(\n Buffer.from(markdownText, 'utf8'),\n 'lab_report_summary.md',\n 'text/markdown'\n);\n\nreturn [{\n json: $('Code: Build Final Markdown Report').item.json,\n binary: {\n data: binaryData\n }\n}];"
},
"typeVersion": 2
},
{
"id": "3cefb973-3f24-4511-a524-069593a959ef",
"name": "Gmail: Email Summary to Patient",
"type": "n8n-nodes-base.gmail",
"position": [
1136,
48
],
"parameters": {
"sendTo": "={{ $json.patient_email }}",
"message": "=<div style=\"font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;\">\n <div style=\"background: #1B2A4A; padding: 20px; border-radius: 8px 8px 0 0;\">\n <h2 style=\"color: white; margin: 0;\">\ud83c\udfe5 Your Lab Report Summary</h2>\n </div>\n <div style=\"background: #f9f9f9; padding: 20px; border: 1px solid #e0e0e0;\">\n <p>Dear <strong>{{ $('Code: Build Final Markdown Report').item.json.patient_name }}</strong>,</p>\n <p>Your lab report has been processed and translated into <strong>{{ $('Code: Build Final Markdown Report').item.json.preferred_language }}</strong>. Please find your easy-to-understand summary attached to this email.</p>\n <div style=\"background: #D6E8F7; border-left: 4px solid #2D5F8A; padding: 12px; margin: 16px 0; border-radius: 4px;\">\n <strong>Report Details:</strong><br/>\n \ud83d\udccb Type: {{ $('Code: Build Final Markdown Report').item.json.report_type }}<br/>\n \ud83d\udcc5 Date: {{ $('Code: Build Final Markdown Report').item.json.report_date }}<br/>\n \ud83c\udfe5 Lab: {{ $('Code: Build Final Markdown Report').item.json.lab_name }}\n </div>\n <p>The summary explains your results in plain language. If you have any concerns about your results, please contact your doctor.</p>\n <p style=\"color: #888; font-size: 12px;\">This summary was generated by AI and is for informational purposes only. Always consult your healthcare provider for medical advice.</p>\n </div>\n</div>",
"options": {
"attachmentsUi": {
"attachmentsBinary": [
{}
]
}
},
"subject": "=Your Lab Report Summary \u2013 {{ $('Code: Build Final Markdown Report').item.json.report_type }} ({{ $('Code: Build Final Markdown Report').item.json.report_date }})"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "48ad1e70-ab5f-48ab-a9e8-89825beb7d55",
"name": "Slack: Alert on Extraction Error",
"type": "n8n-nodes-base.slack",
"position": [
-576,
288
],
"parameters": {
"text": "=\u26a0\ufe0f Lab Report workflow error at AI Extraction step.\nFile: {{ $('Google Drive: Watch for New Lab Report').item.json.name }}\nError: {{ $json.error.message }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "list",
"value": "YOUR_SLACK_CHANNEL_ID",
"cachedResultName": "workflow-errors"
},
"otherOptions": {},
"authentication": "oAuth2"
},
"typeVersion": 2.3
},
{
"id": "62aaa5ab-1aeb-4beb-aa99-50dc7a227ff4",
"name": "Slack: Alert on Simplification Error",
"type": "n8n-nodes-base.slack",
"position": [
-32,
368
],
"parameters": {
"text": "=\u26a0\ufe0f Lab Report workflow error at Plain English step.\nFile: {{ $('Google Drive: Watch for New Lab Report').item.json.name }}\nError: {{ $json.error.message }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "list",
"value": "YOUR_SLACK_CHANNEL_ID",
"cachedResultName": "workflow-errors"
},
"otherOptions": {},
"authentication": "oAuth2"
},
"typeVersion": 2.3
},
{
"id": "3abe305b-a778-4af1-8426-4591469eaf01",
"name": "Slack: Alert on Translation Error",
"type": "n8n-nodes-base.slack",
"position": [
656,
432
],
"parameters": {
"text": "=\u26a0\ufe0f Lab Report workflow error at Translation step.\nFile: {{ $('Google Drive: Watch for New Lab Report').item.json.name }}\nError: {{ $json.error.message }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "list",
"value": "YOUR_SLACK_CHANNEL_ID",
"cachedResultName": "workflow-errors"
},
"otherOptions": {},
"authentication": "oAuth2"
},
"typeVersion": 2.3
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"availableInMCP": false,
"executionOrder": "v1"
},
"versionId": "13b83b68-07c5-45d2-9813-55414dafc953",
"connections": {
"Google Drive: Download PDF": {
"main": [
[
{
"node": "Extract Text: Parse PDF to Raw Text",
"type": "main",
"index": 0
}
]
]
},
"Slack: Alert on Extraction Error": {
"main": [
[
{
"node": "Google Drive: Download PDF",
"type": "main",
"index": 0
}
]
]
},
"Code: Build Final Markdown Report": {
"main": [
[
{
"node": "Code: Convert Report to Markdown File",
"type": "main",
"index": 0
}
]
]
},
"Slack: Alert on Translation Error": {
"main": [
[
{
"node": "Google Drive: Download PDF",
"type": "main",
"index": 0
}
]
]
},
"Code: Attach Plain English Summary": {
"main": [
[
{
"node": "AI: Translate Summary to Patient Language",
"type": "main",
"index": 0
}
]
]
},
"Extract Text: Parse PDF to Raw Text": {
"main": [
[
{
"node": "AI: Extract Structure + Detect Language",
"type": "main",
"index": 0
}
]
]
},
"Slack: Alert on Simplification Error": {
"main": [
[
{
"node": "Google Drive: Download PDF",
"type": "main",
"index": 0
}
]
]
},
"Code: Convert Report to Markdown File": {
"main": [
[
{
"node": "Gmail: Email Summary to Patient",
"type": "main",
"index": 0
}
]
]
},
"AI: Simplify Findings to Plain English": {
"main": [
[
{
"node": "Code: Attach Plain English Summary",
"type": "main",
"index": 0
}
],
[
{
"node": "Slack: Alert on Simplification Error",
"type": "main",
"index": 0
}
]
]
},
"Code: Parse & Sanitise Extraction JSON": {
"main": [
[
{
"node": "AI: Simplify Findings to Plain English",
"type": "main",
"index": 0
}
]
]
},
"Google Drive: Watch for New Lab Report": {
"main": [
[
{
"node": "Google Drive: Download PDF",
"type": "main",
"index": 0
}
]
]
},
"AI: Extract Structure + Detect Language": {
"main": [
[
{
"node": "Code: Parse & Sanitise Extraction JSON",
"type": "main",
"index": 0
}
],
[
{
"node": "Slack: Alert on Extraction Error",
"type": "main",
"index": 0
}
]
]
},
"AI: Translate Summary to Patient Language": {
"main": [
[
{
"node": "Code: Build Final Markdown Report",
"type": "main",
"index": 0
}
],
[
{
"node": "Slack: Alert on Translation Error",
"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.
gmailOAuth2googleDriveOAuth2ApiopenAiApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow watches a Google Drive folder for new lab-report PDFs, extracts their text, uses OpenAI (gpt-4o-mini) to structure and simplify results, translates the summary into the patient’s preferred language, and emails a Markdown report via Gmail, with error notifications…
Source: https://n8n.io/workflows/16427/ — 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.
Some use cases: Sales follow-ups, auto-qualifying leads based on budget, monetizing low-budget leads, and automatic data entry. Ingestion: When a call recording is uploaded to a specific Google Drive
This workflow automatically turns any audio file uploaded to Google Drive into a complete podcast episode. It handles transcription, content generation, blog drafting, social copy creation, thumbnail
Overview
This template is ideal for photographers, graphic designers, and creative professionals who manage large volumes of visual assets. It is also perfect for Digital Asset Managers looking for a customiza
Small teams, solo operators, and security-conscious individuals who receive email attachments from external senders. Useful for freelancers, agencies, HR teams, and anyone handling CVs, invoices, or d