N
n8n Store
Workflow Market
Email_Summarizer

Email_Summarizer

by dk10rkβ€’0 views

Description

Categories

βš™οΈ Automation

Nodes Used

n8n-nodes-base.ifn8n-nodes-base.coden8n-nodes-base.coden8n-nodes-base.gmailn8n-nodes-base.mergen8n-nodes-base.stickyNoten8n-nodes-base.stickyNoten8n-nodes-base.stickyNoten8n-nodes-base.stickyNoten8n-nodes-base.stickyNote
PriceFree
Views0
Last Updated11/28/2025
workflow.json
{
  "id": "7O3XDyjnKZuQ1iOB",
  "meta": {
    "instanceId": "244c1be2296cca5db33ca6c21216dc529b5c3bc58583380120d574740ab6be48",
    "templateCredsSetupCompleted": true
  },
  "name": "Email_Summarizer",
  "tags": [],
  "nodes": [
    {
      "id": "72bc448c-16e2-43f5-a3c3-5332183a91a7",
      "name": "Google Drive Trigger",
      "type": "n8n-nodes-base.googleDriveTrigger",
      "position": [
        416,
        -32
      ],
      "parameters": {
        "event": "fileCreated",
        "options": {},
        "pollTimes": {
          "item": [
            {
              "mode": "everyHour"
            }
          ]
        },
        "triggerOn": "specificFolder",
        "folderToWatch": {
          "__rl": true,
          "mode": "list",
          "value": "1IsQ3KyyfiexcYPlOiAkzYaH4MHI4VBPZ",
          "cachedResultUrl": "https://drive.google.com/drive/folders/1IsQ3KyyfiexcYPlOiAkzYaH4MHI4VBPZ",
          "cachedResultName": "GraphRag"
        }
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "id": "mGPNCCV1nKuyoWiu",
          "name": "Google Drive account"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "6d7063b9-8ff3-4a3a-83a0-b97e4ea64311",
      "name": "Extract from File",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        1088,
        -128
      ],
      "parameters": {
        "options": {},
        "operation": "pdf"
      },
      "typeVersion": 1
    },
    {
      "id": "6794155c-c90a-4876-828f-dfdda5c9ab22",
      "name": "If",
      "type": "n8n-nodes-base.if",
      "position": [
        640,
        -32
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "14105474-d15e-477a-8916-cef9313887f7",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.fileExtension }}",
              "rightValue": "pdf"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "59d342dc-011a-420f-ba22-330fda11c4fd",
      "name": "Extract from File1",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        1088,
        64
      ],
      "parameters": {
        "options": {},
        "operation": "text"
      },
      "typeVersion": 1
    },
    {
      "id": "0b9f534e-b534-4f81-b760-2b95bd9be3f4",
      "name": "Download file",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        864,
        -128
      ],
      "parameters": {
        "fileId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.id }}"
        },
        "options": {},
        "operation": "download"
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "id": "mGPNCCV1nKuyoWiu",
          "name": "Google Drive account"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "1686d72f-6448-496b-b8fb-a98f7a559dc3",
      "name": "Download file1",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        864,
        64
      ],
      "parameters": {
        "fileId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Google Drive Trigger').item.json.parents[0] }}"
        },
        "options": {},
        "operation": "download"
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "id": "mGPNCCV1nKuyoWiu",
          "name": "Google Drive account"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "6f459193-17b1-4313-a251-b56febdaa721",
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "position": [
        1312,
        -32
      ],
      "parameters": {},
      "typeVersion": 3.2
    },
    {
      "id": "84894f45-b8a8-4837-98f4-5eefe64a720f",
      "name": "AI Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        1536,
        -32
      ],
      "parameters": {
        "text": "={{ $json.text }}",
        "options": {
          "systemMessage": "You are a helpful You are a rigorous meeting assistant. Convert the provided meeting text into a project plan.\nReturn ONLY strict JSON with these keys and types:\n- summary: string (<= 40 words)\n- decisions: string[]\n- notes: string[]\n- meeting_sentiment: one of {\"positive\",\"neutral\",\"negative\"}\n- tasks: array of objects {\n   description: string,\n   owner: string | \"TBD\",\n   deadline: ISO date string | \"TBD\",\n   sentiment: one of {\"positive\",\"neutral\",\"negative\"}\n}\nConstraints:\n- No prose, no markdown, no code fences.\n- If unknown, use empty list or \"TBD\".\n- Infer dates only if clearly stated; otherwise \"TBD\".\nassistant"
        },
        "promptType": "define"
      },
      "typeVersion": 2.2
    },
    {
      "id": "2e1d64e1-a23e-432b-b2f2-54b4491bf3be",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        1608,
        192
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini"
        },
        "options": {}
      },
      "credentials": {
        "openAiApi": {
          "id": "q4FyMs2sPCHEzYj0",
          "name": "n8n free OpenAI API credits"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "b37e84e6-4015-40b7-bcfe-745f1332f156",
      "name": "Send a message",
      "type": "n8n-nodes-base.gmail",
      "position": [
        2336,
        -32
      ],
      "webhookId": "4245dd03-5fc2-4a3b-9974-1ccd4c89099d",
      "parameters": {
        "sendTo": "={{ $('Google Drive Trigger').item.json.lastModifyingUser.emailAddress }}",
        "message": "={{ $json.email_html }}",
        "options": {},
        "subject": "=Meeting Summary β€” {{$json.meeting_sentiment}} tone"
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "f4mgBX1LUYyUTArB",
          "name": "Gmail account"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "496624bb-9827-4fb3-9bc2-355265216e15",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -16,
        -432
      ],
      "parameters": {
        "width": 384,
        "height": 832,
        "content": "## 🧩 AI Meeting Summary & Email Automation\n\nAutomatically turn meeting files (PDF or TXT) uploaded to Google Drive into structured summaries with decisions, notes, and action items β€” then email them beautifully formatted via Gmail.\n\n---\n\n### 🧠 How It Works\n1. Watch a Google Drive folder for new files  \n2. Extract text (from PDF or TXT)  \n3. Send content to OpenAI GPT-4o-mini  \n4. Parse and group tasks by sentiment  \n5. Build an HTML summary  \n6. Email it automatically\n\n---\n\n### βš™οΈ Setup\n1. Connect Google Drive, OpenAI, and Gmail credentials  \n2. Point the Drive Trigger to your β€œMeetings” folder  \n3. Paste the system prompt into the AI node  \n4. Set Gmail message field to `{{$json.email_html}}`  \n5. Drop a file in the folder β€” everything runs end-to-end!\n\n---\n\nπŸ’‘ *Perfect for team syncs, standups, sprint reviews, or client calls.*\n"
      },
      "typeVersion": 1
    },
    {
      "id": "309556b1-a5ca-498c-a5ad-9d9024e13147",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        360,
        -496
      ],
      "parameters": {
        "color": 6,
        "width": 432,
        "height": 624,
        "content": "## Google Drive Trigger + File Routing\n\n**Watch New Files (Meeting Notes Folder)**  \nStarts the workflow whenever a new file appears in your Drive folder.\n\n**Check File Type (PDF or Text)**  \nRoutes files by MIME type:  \n- βœ… `application/pdf` β†’ PDF extraction path  \n- βœ… `text/plain` β†’ TXT extraction path  \n\n---\n\nπŸ’‘ *Keeps the workflow flexible for multiple file formats.*\n"
      },
      "typeVersion": 1
    },
    {
      "id": "9b04ab30-9935-4db6-b2fb-029567ed167e",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1472,
        -448
      ],
      "parameters": {
        "color": 6,
        "width": 352,
        "height": 416,
        "content": "## AI Summarization\n\nThis step is where the AI does all the heavy lifting.  \nIt takes the extracted meeting text and uses **OpenAI GPT-4o-mini** to:\n\n- Read through the entire meeting transcript  \n- Identify and summarize key decisions and discussion points  \n- Extract important notes and insights  \n- Detect overall **meeting sentiment** (positive, neutral, or negative)  \n- Generate structured **tasks** with owners, deadlines, and sentiment tags  \n\nThe output is a **strict JSON object**, which ensures the next nodes can automatically parse, format, and send the results without manual cleanup.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "a4d2bdfa-8b93-423b-a70d-06386279e994",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1840,
        112
      ],
      "parameters": {
        "color": 6,
        "height": 384,
        "content": "##  Validate & Structure Output\n\n**Normalize AI Output (Code Node)**  \n- Parses JSON safely  \n- Groups tasks by sentiment (`positive`, `neutral`, `negative`)  \n- Builds a `totals` object for quick statistics  \n- Prevents null/undefined errors in next step\n\n---\n\nπŸ’‘ *Ensures every summary email is structured and reliable.*\n"
      },
      "typeVersion": 1
    },
    {
      "id": "b5ed5b24-bcac-4514-8ab1-64b52785034a",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2048,
        -368
      ],
      "parameters": {
        "color": 6,
        "width": 256,
        "height": 320,
        "content": "## Generate HTML Summary\n\n**Generate HTML Report (Code Node)**  \nConverts the normalized JSON into a professional email-ready layout.\n\nIncludes:\n- πŸ“ Summary  \n- πŸ’‘ Key Decisions  \n- πŸ—’οΈ Notes  \n- βœ… / βšͺ / ❌ grouped tasks  \n- Inline CSS for Gmail readability\n\n---\n\nπŸ“„ Output variable: `email_html`\n"
      },
      "typeVersion": 1
    },
    {
      "id": "49eb2ca5-6fb7-4659-adf6-bbbaac5c5bce",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2560,
        -128
      ],
      "parameters": {
        "color": 6,
        "width": 320,
        "height": 352,
        "content": "##  Send AI-Generated Report\n\n**Send AI Meeting Summary Email (Gmail)**  \n- **Email Type:** HTML  \n- **Message:** `{{$json.email_html}}`  \n- **Subject Example:**  \n  `AI Summary – {{$json.meeting_sentiment}} | βœ…{{$json.totals.positive}} βšͺ{{$json.totals.neutral}} ❌{{$json.totals.negative}}`\n\n---\n\nπŸ“¬ *Delivers the final meeting digest automatically to your inbox.*\n"
      },
      "typeVersion": 1
    },
    {
      "id": "83c30f5d-e519-4628-a42b-d278a0a3679c",
      "name": "Data Validation",
      "type": "n8n-nodes-base.code",
      "position": [
        1888,
        -32
      ],
      "parameters": {
        "jsCode": "/**\n * Robust parser for different upstream shapes:\n * - n8n AI Agent:        item.json.output (string)\n * - OpenAI Chat (HTTP):  item.json.body.choices[0].message.content\n * - OpenAI Chat (direct):item.json.choices[0].message.content\n * - Already an object:   item.json (if the LLM node already parsed JSON)\n */\nconst outputs = [];\n\nfor (const item of items) {\n  let content;   // string with JSON\n  let data;      // parsed object\n\n  // 1) Try common shapes\n  if (typeof item.json?.output === 'string') {\n    // n8n \"AI Agent\" node output\n    content = item.json.output;\n  } else if (typeof item.json?.choices?.[0]?.message?.content === 'string') {\n    // direct Chat Completions shape\n    content = item.json.choices[0].message.content;\n  } else if (typeof item.json?.body?.choices?.[0]?.message?.content === 'string') {\n    // HTTP Request node -> body -> choices\n    content = item.json.body.choices[0].message.content;\n  } else if (typeof item.json === 'string') {\n    // just a raw string\n    content = item.json;\n  } else if (typeof item.json === 'object' && item.json !== null && (\n             item.json.summary || item.json.tasks || item.json.decisions || item.json.notes)) {\n    // already parsed into an object with expected keys\n    data = item.json;\n  }\n\n  // 2) If we still need to parse, do it\n  if (!data) {\n    if (typeof content !== 'string') {\n      throw new Error('Could not locate LLM text output in upstream node. Check: output, choices[0].message.content, or body.choices...');\n    }\n    try {\n      data = JSON.parse(content);\n    } catch {\n      throw new Error('LLM did not return valid JSON. Verify the system prompt and response_format=json_object.');\n    }\n  }\n\n  // 3) Normalize the schema\n  if (typeof data.summary !== 'string') data.summary = '';\n  if (!Array.isArray(data.decisions)) data.decisions = [];\n  if (!Array.isArray(data.notes)) data.notes = [];\n  if (!['positive','neutral','negative'].includes(data.meeting_sentiment)) {\n    data.meeting_sentiment = 'neutral';\n  }\n  if (!Array.isArray(data.tasks)) data.tasks = [];\n\n  data.tasks = data.tasks.map(t => ({\n    description: t?.description ?? '',\n    owner: t?.owner ?? 'TBD',\n    deadline: t?.deadline ?? 'TBD',\n    sentiment: ['positive','neutral','negative'].includes(t?.sentiment) ? t.sentiment : 'neutral'\n  }));\n\n  // Optional grouped and totals\n  const grouped = { positive: [], neutral: [], negative: [] };\n  for (const t of data.tasks) grouped[t.sentiment].push(t);\n  data.tasks_grouped = grouped;\n  data.totals = {\n    positive: grouped.positive.length,\n    neutral: grouped.neutral.length,\n    negative: grouped.negative.length\n  };\n\n  outputs.push({ json: data });\n}\n\nreturn outputs;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "4214d550-f3f0-40e0-87dc-3aab71ad931b",
      "name": "Data Preparation",
      "type": "n8n-nodes-base.code",
      "position": [
        2112,
        -32
      ],
      "parameters": {
        "jsCode": "// This Code node expects your previous node to output ONE item\n// with keys: summary, decisions[], notes[], meeting_sentiment,\n// tasks_grouped{positive[],neutral[],negative[]}, totals{...}.\n\n// helper funcs\nconst esc = (s) => String(s ?? '')\n  .replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')\n  .replace(/\"/g,'&quot;').replace(/'/g,'&#39;');\n\nconst li = (s) => `<li>${esc(s)}</li>`;\nconst liTask = (t) =>\n  `<li><strong>${esc(t.description)}</strong> β€” ${esc(t.owner)} <em>(Deadline: ${esc(t.deadline)})</em></li>`;\n\n// read current item\nconst d = $json;\n\n// fallbacks if any section is missing\nconst decisions = Array.isArray(d.decisions) ? d.decisions : [];\nconst notes     = Array.isArray(d.notes) ? d.notes : [];\nconst tg        = d.tasks_grouped || { positive: [], neutral: [], negative: [] };\nconst totals    = d.totals || {\n  positive: tg.positive?.length ?? 0,\n  neutral:  tg.neutral?.length  ?? 0,\n  negative: tg.negative?.length ?? 0\n};\n\n// build section HTML safely\nconst decisionsHtml = decisions.map(li).join('');\nconst notesHtml     = notes.map(li).join('');\nconst posHtml       = (tg.positive || []).map(liTask).join('');\nconst neuHtml       = (tg.neutral  || []).map(liTask).join('');\nconst negHtml       = (tg.negative || []).map(liTask).join('');\n\n// MINIMAL, COMPATIBLE HTML (no handlebars, no fancy CSS needed)\nconst html = `<!DOCTYPE html>\n<html>\n  <body style=\"font-family: Arial, Helvetica, sans-serif; color:#1f2937; line-height:1.55; margin:0; padding:16px;\">\n    <h2 style=\"margin:0 0 8px 0;\">πŸ“ Meeting Summary</h2>\n    <p style=\"margin:0 0 8px 0;\">${esc(d.summary || '')}</p>\n\n    <p style=\"margin:8px 0;\">\n       <strong>Positive:</strong> ${totals.positive}\n      &nbsp;β€’&nbsp; <strong>Neutral:</strong> ${totals.neutral}\n      &nbsp;β€’&nbsp; <strong>Negative:</strong> ${totals.negative}\n    </p>\n\n    ${decisionsHtml ? `\n      <h3 style=\"margin:16px 0 6px;\">πŸ’‘ Key Decisions</h3>\n      <ul style=\"margin:0 0 12px 18px;\">${decisionsHtml}</ul>\n    ` : ''}\n\n    ${notesHtml ? `\n      <h3 style=\"margin:16px 0 6px;\">πŸ—’οΈ Notes</h3>\n      <ul style=\"margin:0 0 12px 18px;\">${notesHtml}</ul>\n    ` : ''}\n\n    ${posHtml ? `\n      <h3 style=\"margin:16px 0 6px;\">βœ… Positive Tasks</h3>\n      <ul style=\"margin:0 0 12px 18px;\">${posHtml}</ul>\n    ` : ''}\n\n    ${neuHtml ? `\n      <h3 style=\"margin:16px 0 6px;\">βšͺ Neutral Tasks</h3>\n      <ul style=\"margin:0 0 12px 18px;\">${neuHtml}</ul>\n    ` : ''}\n\n    ${negHtml ? `\n      <h3 style=\"margin:16px 0 6px;\">❌ Negative Tasks</h3>\n      <ul style=\"margin:0 0 12px 18px;\">${negHtml}</ul>\n    ` : ''}\n\n    <p style=\"font-size:12px; color:#6b7280; margin-top:16px;\">Sent by n8n β€’ AI Meeting Assistant</p>\n  </body>\n</html>`;\n\n// output same data + ready-to-send html\nreturn [{ json: { ...d, email_html: html } }];\n"
      },
      "typeVersion": 2
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "dea36ea5-7b21-4251-87b9-e9d64ed31e7b",
  "connections": {
    "If": {
      "main": [
        [
          {
            "node": "Download file",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Download file1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent": {
      "main": [
        [
          {
            "node": "Data Validation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download file": {
      "main": [
        [
          {
            "node": "Extract from File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download file1": {
      "main": [
        [
          {
            "node": "Extract from File1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Data Validation": {
      "main": [
        [
          {
            "node": "Data Preparation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Data Preparation": {
      "main": [
        [
          {
            "node": "Send a message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract from File": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Extract from File1": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Google Drive Trigger": {
      "main": [
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

η›Έε…³ε·₯作桁