{
  "id": "WORKFLOW_ID_PLACEHOLDER",
  "name": "Classify Gmail emails with AI and auto-label with smart reply drafts using Ollama",
  "tags": [],
  "nodes": [
    {
      "id": "e654e5c9-b2d2-4fda-b7c9-848a63fc2d93",
      "name": "Sticky Note - Main Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -400,
        -112
      ],
      "parameters": {
        "color": 4,
        "width": 740,
        "height": 1000,
        "content": "## Classify Gmail emails with AI and auto-label with smart reply drafts\n\nAutomatically classify every incoming email using a local AI model (Ollama), apply Gmail labels, and generate smart reply drafts for important messages \u2014 zero paid APIs required.\n\n### How it works\n1. **Gmail Trigger** watches your inbox for new unread emails every 30 minutes.\n2. **Extract Email Data** parses the sender, subject, body, and metadata into clean fields.\n3. **AI Classifier** (Ollama, local) analyzes the email and classifies it into one of 6 categories: \ud83d\udd34 Urgent, \ud83d\udccb Action Required, \ud83d\udcac Follow-up, \ud83d\udcf0 Newsletter, \ud83e\udd16 Automated, or \ud83d\udeab Spam/Promotional.\n4. **Smart Router** sends each email down its dedicated path based on classification.\n5. **Gmail Labels** are automatically applied so your inbox is organized without lifting a finger.\n6. **Reply Drafts** are generated by AI for Urgent and Action Required emails \u2014 ready for you to review, edit, and send.\n7. **Summary Log** tracks every processed email in Google Sheets for analytics and review.\n\n### Setup steps\n1. **Gmail OAuth** \u2014 Connect your Google account with read, modify, and compose permissions.\n2. **Create Gmail Labels** \u2014 In Gmail, manually create these labels:\n   - `\ud83d\udd34 Urgent`, `\ud83d\udccb Action Required`, `\ud83d\udcac Follow-up`, `\ud83d\udcf0 Newsletter`, `\ud83e\udd16 Automated`, `\ud83d\udeab Spam-Promo`\n3. **Get Label IDs** \u2014 Use Gmail API or n8n to find each label's ID. Update the `labelIds` in each Gmail Label node.\n4. **Ollama** \u2014 Ensure Ollama is running locally with your preferred model pulled (default: `mistral`). Change the model name in the Ollama Chat Model node if needed.\n5. **Google Sheets** (optional) \u2014 Connect your credential and set a spreadsheet ID for the logging node.\n6. **Test** \u2014 Send yourself test emails (urgent, newsletter, promotional) and run manually to verify accuracy.\n\n### Customization\n- Swap `mistral` for `llama3`, `gemma2`, or any Ollama model.\n- Add more categories by editing the AI prompt and adding Switch outputs.\n- Change the reply draft tone (formal, casual, friendly) in the AI prompt.\n- Add Slack/Telegram notifications for urgent emails.\n- Add auto-archive for newsletters and spam.\n- Decrease the polling interval for near-real-time processing.\n- Add sender whitelist/blacklist logic before the AI node."
      },
      "typeVersion": 1
    },
    {
      "id": "9fb47ffc-8479-4013-80dd-ea78e55d6227",
      "name": "Sticky Note - Email Intake",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        160,
        960
      ],
      "parameters": {
        "color": 6,
        "width": 560,
        "height": 260,
        "content": "## \ud83d\udce5 Email Intake\nGmail trigger watches inbox for unread emails, then extracts and cleans email data"
      },
      "typeVersion": 1
    },
    {
      "id": "6c54a558-173b-44b8-80f8-38825cb5d8c6",
      "name": "Sticky Note - AI Classification",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        768,
        960
      ],
      "parameters": {
        "color": 6,
        "width": 520,
        "height": 308,
        "content": "## \ud83e\udd16 AI Classification\nOllama analyzes email content and returns category, priority, and reply draft"
      },
      "typeVersion": 1
    },
    {
      "id": "c3a16e2d-adc1-4e00-a31a-c409814efb02",
      "name": "Sticky Note - Route and Label",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1328,
        768
      ],
      "parameters": {
        "color": 6,
        "width": 960,
        "height": 1004,
        "content": "## \ud83d\udd00 Route, Label & Draft\nSwitch by category, apply Gmail labels, create reply drafts for important emails"
      },
      "typeVersion": 1
    },
    {
      "id": "59d64a60-80df-426e-a485-43872e4bead0",
      "name": "Sticky Note - Logging",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2352,
        960
      ],
      "parameters": {
        "color": 6,
        "width": 400,
        "height": 296,
        "content": "## \ud83d\udcca Logging\nEvery processed email is logged to Google Sheets for analytics"
      },
      "typeVersion": 1
    },
    {
      "id": "31261316-e9da-490d-9bfa-3f3d30364b22",
      "name": "Sticky Note - Warning Labels",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1344,
        1840
      ],
      "parameters": {
        "color": 3,
        "width": 360,
        "content": "## \u26a0\ufe0f Create Gmail Labels First\nYou must manually create these labels in Gmail before activating:\n`\ud83d\udd34 Urgent`, `\ud83d\udccb Action Required`, `\ud83d\udcac Follow-up`, `\ud83d\udcf0 Newsletter`, `\ud83e\udd16 Automated`, `\ud83d\udeab Spam-Promo`\nThen update the Label IDs in each labeling node."
      },
      "typeVersion": 1
    },
    {
      "id": "b0f03be3-f1c2-4f44-8ec9-ee2fd8865420",
      "name": "Sticky Note - Warning Ollama",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        784,
        1344
      ],
      "parameters": {
        "color": 3,
        "width": 300,
        "height": 140,
        "content": "## \u26a0\ufe0f Check Ollama Model\nDefault model: `mistral`. Make sure it's pulled:\n`ollama pull mistral`\nOr change to `llama3`, `gemma2`, etc."
      },
      "typeVersion": 1
    },
    {
      "id": "db9674d4-8f0e-4594-85c3-fd60cce764c6",
      "name": "\ud83d\udce5 Gmail Trigger",
      "type": "n8n-nodes-base.gmailTrigger",
      "position": [
        208,
        1088
      ],
      "parameters": {
        "simple": false,
        "filters": {
          "readStatus": "unread"
        },
        "options": {},
        "pollTimes": {
          "item": [
            {
              "mode": "everyX",
              "unit": "minutes",
              "value": 30
            }
          ]
        }
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "950c6313-8ef5-4f82-a8b0-2918e868bbfe",
      "name": "\ud83d\udd27 Extract Email Data",
      "type": "n8n-nodes-base.code",
      "position": [
        448,
        1088
      ],
      "parameters": {
        "jsCode": "// Extract and clean email data for AI processing\nconst items = $input.all();\nconst processedEmails = [];\n\nfor (const item of items) {\n  const email = item.json;\n  \n  // ============================================\n  // SAFELY EXTRACT HEADERS (handles all formats)\n  // ============================================\n  \n  function safeString(value) {\n    if (!value) return '';\n    if (typeof value === 'string') return value;\n    if (Array.isArray(value)) return value.map(v => safeString(v)).join(', ');\n    if (typeof value === 'object') {\n      // Gmail sometimes returns { name: \"John\", email: \"user@example.com\" }\n      if (value.value) return safeString(value.value);\n      if (value.text) return safeString(value.text);\n      if (value.email && value.name) return `${value.name} <${value.email}>`;\n      if (value.email) return value.email;\n      if (value.address) return value.address;\n      return JSON.stringify(value);\n    }\n    return String(value);\n  }\n  \n  function getHeader(headers, name) {\n    if (!headers || !Array.isArray(headers)) return '';\n    const found = headers.find(h => \n      h && h.name && h.name.toLowerCase() === name.toLowerCase()\n    );\n    return found ? safeString(found.value) : '';\n  }\n  \n  // Try multiple Gmail node output formats\n  let fromHeader = '';\n  let subjectHeader = '';\n  let dateHeader = '';\n  let toHeader = '';\n  let ccHeader = '';\n  let replyToHeader = '';\n  \n  // Format 1: Gmail Trigger v1 (flat fields)\n  if (email.from) {\n    fromHeader = safeString(email.from);\n    subjectHeader = safeString(email.subject || '');\n    dateHeader = safeString(email.date || email.internalDate || '');\n    toHeader = safeString(email.to || '');\n    ccHeader = safeString(email.cc || '');\n    replyToHeader = safeString(email.replyTo || '');\n  }\n  \n  // Format 2: Gmail API raw (payload.headers array)\n  if (email.payload && email.payload.headers) {\n    fromHeader = fromHeader || getHeader(email.payload.headers, 'from');\n    subjectHeader = subjectHeader || getHeader(email.payload.headers, 'subject');\n    dateHeader = dateHeader || getHeader(email.payload.headers, 'date');\n    toHeader = toHeader || getHeader(email.payload.headers, 'to');\n    ccHeader = ccHeader || getHeader(email.payload.headers, 'cc');\n    replyToHeader = replyToHeader || getHeader(email.payload.headers, 'reply-to');\n  }\n  \n  // Format 3: Some Gmail nodes use headers directly\n  if (email.headers) {\n    const h = email.headers;\n    fromHeader = fromHeader || safeString(h.from || h.From || '');\n    subjectHeader = subjectHeader || safeString(h.subject || h.Subject || '');\n    dateHeader = dateHeader || safeString(h.date || h.Date || '');\n    toHeader = toHeader || safeString(h.to || h.To || '');\n    ccHeader = ccHeader || safeString(h.cc || h.Cc || '');\n  }\n  \n  // Format 4: labelIds at top level (Gmail Trigger v2)\n  if (!fromHeader && email.labelIds) {\n    fromHeader = safeString(email.from || '');\n    subjectHeader = safeString(email.subject || '');\n  }\n  \n  // ============================================\n  // PARSE SENDER NAME AND EMAIL\n  // ============================================\n  let senderName = '';\n  let senderEmail = '';\n  let senderDomain = '';\n  \n  // Ensure fromHeader is definitely a string before regex\n  fromHeader = String(fromHeader || '');\n  subjectHeader = String(subjectHeader || '');\n  \n  try {\n    // Try \"Name <email>\" format\n    const senderMatch = fromHeader.match(/^\"?([^\"<]+)\"?\\s*<?([^>]*)>?$/);\n    if (senderMatch && senderMatch[2]) {\n      senderName = senderMatch[1].trim();\n      senderEmail = senderMatch[2].trim();\n    } else if (fromHeader.includes('@')) {\n      // Just an email address\n      senderEmail = fromHeader.trim();\n      senderName = senderEmail.split('@')[0];\n    } else {\n      senderName = fromHeader;\n      senderEmail = fromHeader;\n    }\n    \n    senderDomain = senderEmail.includes('@') ? senderEmail.split('@')[1] : '';\n  } catch (e) {\n    senderName = fromHeader;\n    senderEmail = fromHeader;\n    senderDomain = '';\n  }\n  \n  // ============================================\n  // EXTRACT BODY TEXT\n  // ============================================\n  let bodyText = '';\n  \n  // Try direct text fields first (most Gmail trigger versions)\n  if (email.text) {\n    bodyText = safeString(email.text);\n  } else if (email.textPlain) {\n    bodyText = safeString(email.textPlain);\n  } else if (email.snippet) {\n    bodyText = safeString(email.snippet);\n  } else if (email.textHtml) {\n    bodyText = safeString(email.textHtml);\n  }\n  \n  // Try payload body\n  if (!bodyText && email.payload) {\n    if (email.payload.body && email.payload.body.data) {\n      try {\n        bodyText = Buffer.from(email.payload.body.data, 'base64').toString('utf-8');\n      } catch (e) {}\n    }\n    \n    // Try multipart\n    if (!bodyText && email.payload.parts) {\n      for (const part of email.payload.parts) {\n        if (part.mimeType === 'text/plain' && part.body && part.body.data) {\n          try {\n            bodyText = Buffer.from(part.body.data, 'base64').toString('utf-8');\n            break;\n          } catch (e) {}\n        }\n      }\n      // Fallback to HTML part\n      if (!bodyText) {\n        for (const part of email.payload.parts) {\n          if (part.mimeType === 'text/html' && part.body && part.body.data) {\n            try {\n              bodyText = Buffer.from(part.body.data, 'base64').toString('utf-8');\n              break;\n            } catch (e) {}\n          }\n        }\n      }\n    }\n  }\n  \n  // Use snippet as last resort\n  if (!bodyText && email.snippet) {\n    bodyText = safeString(email.snippet);\n  }\n  \n  // Clean body text\n  bodyText = String(bodyText || '')\n    .replace(/<[^>]+>/g, ' ')       // Strip HTML\n    .replace(/\\r\\n/g, '\\n')         // Normalize newlines\n    .replace(/\\n{3,}/g, '\\n\\n')     // Max 2 newlines\n    .replace(/&nbsp;/g, ' ')\n    .replace(/&amp;/g, '&')\n    .replace(/&lt;/g, '<')\n    .replace(/&gt;/g, '>')\n    .replace(/&quot;/g, '\"')\n    .replace(/&#39;/g, \"'\")\n    .replace(/\\s+/g, ' ')           // Collapse whitespace\n    .trim()\n    .substring(0, 3000);            // Limit for AI\n  \n  // ============================================\n  // DETECT FLAGS\n  // ============================================\n  const hasAttachments = !!(\n    (email.payload && email.payload.parts && email.payload.parts.some(p => p.filename && p.filename.length > 0)) ||\n    email.attachments ||\n    (Array.isArray(email.attachments) && email.attachments.length > 0)\n  );\n  \n  const isReply = subjectHeader.toLowerCase().startsWith('re:');\n  const isForward = subjectHeader.toLowerCase().startsWith('fwd:') || subjectHeader.toLowerCase().startsWith('fw:');\n  \n  // ============================================\n  // BUILD OUTPUT\n  // ============================================\n  processedEmails.push({\n    json: {\n      // Gmail metadata\n      gmailId: email.id || email.messageId || '',\n      threadId: email.threadId || '',\n      labelIds: email.labelIds || [],\n      \n      // Parsed fields\n      from: fromHeader,\n      senderName: senderName,\n      senderEmail: senderEmail,\n      senderDomain: senderDomain,\n      to: toHeader,\n      cc: ccHeader,\n      replyTo: replyToHeader || senderEmail,\n      subject: subjectHeader,\n      date: dateHeader,\n      \n      // Content\n      bodyText: bodyText,\n      bodyPreview: bodyText.substring(0, 300),\n      \n      // Flags\n      hasAttachments: hasAttachments,\n      isReply: isReply,\n      isForward: isForward,\n      \n      // Processing\n      processedAt: new Date().toISOString()\n    }\n  });\n}\n\nif (processedEmails.length === 0) {\n  return [{ json: { error: 'No emails to process', processedAt: new Date().toISOString() } }];\n}\n\nreturn processedEmails;"
      },
      "typeVersion": 2
    },
    {
      "id": "ce560c12-b054-404e-be51-d210d1b677d1",
      "name": "\ud83e\udd16 AI Email Classifier",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        800,
        1088
      ],
      "parameters": {
        "text": "=Classify this email and generate a reply draft if needed.\n\n--- EMAIL DATA ---\nFrom: {{ $json.from }}\nTo: {{ $json.to }}\nCC: {{ $json.cc }}\nSubject: {{ $json.subject }}\nDate: {{ $json.date }}\nHas Attachments: {{ $json.hasAttachments }}\nIs Reply: {{ $json.isReply }}\nIs Forward: {{ $json.isForward }}\nSender Domain: {{ $json.senderDomain }}\n\n--- EMAIL BODY ---\n{{ $json.bodyText }}\n\n--- CLASSIFY AND RESPOND ---",
        "options": {
          "systemMessage": "You are an expert email triage assistant. Your job is to classify incoming emails and generate appropriate reply drafts for important ones.\n\n---\n\nEMAIL CATEGORIES (choose exactly one):\n\n1. urgent\n   - Requires immediate attention or response within hours\n   - Time-sensitive requests, deadlines today/tomorrow\n   - Boss/client escalations, critical issues\n   - Payment/billing problems that need immediate action\n   - Meeting in the next few hours that needs confirmation\n   - Keywords: ASAP, urgent, immediately, deadline, critical, emergency, end of day, EOD\n\n2. action_required\n   - Needs a response or action but not time-critical\n   - Meeting requests, document reviews, approval requests\n   - Questions that need thoughtful answers\n   - Project updates requiring input\n   - Invitations needing RSVP\n   - Collaboration requests\n\n3. follow_up\n   - Informational but may need future action\n   - Status updates, FYI emails\n   - Shared documents for review later\n   - Introductions and networking\n   - Ongoing conversation threads\n   - Thank you emails that might need acknowledgment\n\n4. newsletter\n   - Subscribed content, digests, roundups\n   - Blog post notifications\n   - Industry news, product updates from services you use\n   - Weekly/monthly summaries\n   - Educational content, webinar invites\n   - Has unsubscribe link\n\n5. automated\n   - System-generated notifications\n   - Order confirmations, shipping updates\n   - Password resets, login alerts\n   - Calendar notifications\n   - CI/CD pipeline alerts\n   - Monitoring alerts, server notifications\n   - Sent from noreply@ or notification@ addresses\n\n6. spam_promo\n   - Unsolicited marketing, cold outreach you did not request\n   - Promotional offers, discount codes\n   - \"You've been selected\" type emails\n   - Link-heavy promotional content\n   - Mass marketing campaigns\n   - Emails from unknown senders with salesy language\n\n---\n\nCLASSIFICATION RULES:\n\n1. Look at the sender domain \u2014 noreply@, notification@, marketing@ are strong signals.\n2. Check the subject line for urgency keywords.\n3. Analyze the body for calls to action, deadlines, or questions directed at the recipient.\n4. If the email is a reply in a thread (is_reply = true), it's more likely action_required or follow_up.\n5. Forwarded emails often need action or are FYI.\n6. Short emails with direct questions = action_required or urgent.\n7. Long emails with links and images = newsletter or spam_promo.\n8. When in doubt between newsletter and spam_promo, check if the sender seems legitimate.\n9. When in doubt between action_required and follow_up, lean toward action_required.\n\n---\n\nREPLY DRAFT RULES:\n\n- Generate a reply draft ONLY for: urgent, action_required, and follow_up categories.\n- Do NOT generate replies for: newsletter, automated, spam_promo.\n- Reply tone: Professional but warm. Not robotic.\n- Reply length: 30-80 words. Short and actionable.\n- Start with context acknowledgment, then address the ask.\n- End with a clear next step or question.\n- Do NOT include subject line in the reply body.\n- Do NOT include greetings like \"Dear\" \u2014 use first name.\n- Sign off with just \"Best,\" or \"Thanks,\" (the user will add their name).\n\n---\n\nPRIORITY SCORING:\n- 1 = Low (newsletters, automated, spam)\n- 2 = Medium (follow-up, informational)\n- 3 = High (action required, needs response)\n- 4 = Critical (urgent, time-sensitive)\n\n---\n\nRESPOND WITH ONLY RAW JSON. No markdown fences. No explanation. Start with { end with }.\n\n{\n  \"category\": \"urgent\" or \"action_required\" or \"follow_up\" or \"newsletter\" or \"automated\" or \"spam_promo\",\n  \"priority\": 1 to 4,\n  \"confidence\": 0.0 to 1.0,\n  \"reason\": \"One sentence explaining why this classification was chosen\",\n  \"summary\": \"2-3 sentence summary of what the email is about\",\n  \"keyAction\": \"What the recipient needs to do (if anything)\" or \"No action needed\",\n  \"replyDraft\": \"The suggested reply text\" or \"\",\n  \"replySubject\": \"Re: original subject\" or \"\",\n  \"suggestedLabel\": \"\ud83d\udd34 Urgent\" or \"\ud83d\udccb Action Required\" or \"\ud83d\udcac Follow-up\" or \"\ud83d\udcf0 Newsletter\" or \"\ud83e\udd16 Automated\" or \"\ud83d\udeab Spam-Promo\",\n  \"shouldAutoArchive\": true or false,\n  \"sentimentTone\": \"positive\" or \"neutral\" or \"negative\" or \"urgent\"\n}"
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 2.1
    },
    {
      "id": "abdbf241-4eb7-47b4-983d-23da8b06848f",
      "name": "\ud83c\udfaf Extract Classification",
      "type": "n8n-nodes-base.code",
      "position": [
        1088,
        1088
      ],
      "parameters": {
        "jsCode": "// Extract AI classification from response\nconst emailData = $('\ud83d\udd27 Extract Email Data').item.json;\nconst rawAiOutput = $json;\n\nlet classData = {};\n\nfunction extractJSON(str) {\n  if (typeof str !== 'string') return null;\n  let cleaned = str.replace(/```json\\s*/gi, '').replace(/```\\s*/g, '').trim();\n  try { return JSON.parse(cleaned); } catch (e) {\n    const match = cleaned.match(/\\{[\\s\\S]*\"category\"\\s*:[\\s\\S]*\\}/);\n    if (match) { try { return JSON.parse(match[0]); } catch (e2) { return null; } }\n    return null;\n  }\n}\n\nfunction findAIResponse(obj, depth = 0) {\n  if (depth > 5 || !obj || typeof obj !== 'object') return null;\n  if (obj.category && typeof obj.category === 'string') return obj;\n  const props = ['output', 'text', 'message', 'content', 'response', 'result', 'data', 'json', 'kwargs', 'lc_kwargs'];\n  for (const prop of props) {\n    if (obj[prop] !== undefined) {\n      if (typeof obj[prop] === 'string') { const p = extractJSON(obj[prop]); if (p && p.category) return p; }\n      else if (typeof obj[prop] === 'object') { const f = findAIResponse(obj[prop], depth + 1); if (f) return f; }\n    }\n  }\n  for (const key of Object.keys(obj)) {\n    if (props.includes(key)) continue;\n    const val = obj[key];\n    if (typeof val === 'string' && val.includes('\"category\"')) { const p = extractJSON(val); if (p && p.category) return p; }\n    else if (typeof val === 'object' && val !== null) { const f = findAIResponse(val, depth + 1); if (f) return f; }\n  }\n  return null;\n}\n\ntry {\n  if (rawAiOutput && rawAiOutput.category) { classData = rawAiOutput; }\n  else { const found = findAIResponse(rawAiOutput); if (found) classData = found; }\n  if (!classData.category) { const s = JSON.stringify(rawAiOutput); const p = extractJSON(s); if (p && p.category) classData = p; }\n} catch (error) {}\n\nconst validCategories = ['urgent', 'action_required', 'follow_up', 'newsletter', 'automated', 'spam_promo'];\n\nif (!classData.category || !validCategories.includes(classData.category)) {\n  classData = {\n    category: 'follow_up',\n    priority: 2,\n    confidence: 0.5,\n    reason: 'Could not parse AI response \u2014 defaulting to follow_up',\n    summary: emailData.bodyPreview || 'Email content',\n    keyAction: 'Review manually',\n    replyDraft: '',\n    replySubject: '',\n    suggestedLabel: '\ud83d\udcac Follow-up',\n    shouldAutoArchive: false,\n    sentimentTone: 'neutral'\n  };\n}\n\nreturn {\n  json: {\n    // Email data\n    gmailId: emailData.gmailId,\n    threadId: emailData.threadId,\n    from: emailData.from,\n    senderName: emailData.senderName,\n    senderEmail: emailData.senderEmail,\n    senderDomain: emailData.senderDomain,\n    to: emailData.to,\n    subject: emailData.subject,\n    date: emailData.date,\n    bodyPreview: emailData.bodyPreview,\n    hasAttachments: emailData.hasAttachments,\n    isReply: emailData.isReply,\n    replyTo: emailData.replyTo,\n    \n    // AI classification\n    category: classData.category,\n    priority: classData.priority || 2,\n    confidence: parseFloat(classData.confidence) || 0,\n    reason: classData.reason || '',\n    summary: classData.summary || '',\n    keyAction: classData.keyAction || '',\n    replyDraft: classData.replyDraft || '',\n    replySubject: classData.replySubject || `Re: ${emailData.subject}`,\n    suggestedLabel: classData.suggestedLabel || '\ud83d\udcac Follow-up',\n    shouldAutoArchive: classData.shouldAutoArchive === true,\n    sentimentTone: classData.sentimentTone || 'neutral',\n    \n    // Meta\n    processedAt: new Date().toISOString()\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "24539ed4-1050-4e06-a2d3-3bef0ddaedb9",
      "name": "\ud83d\udd00 Route by Category",
      "type": "n8n-nodes-base.switch",
      "position": [
        1344,
        1088
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": false,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.category }}",
                    "rightValue": "urgent"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": false,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.category }}",
                    "rightValue": "action_required"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": false,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.category }}",
                    "rightValue": "follow_up"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": false,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.category }}",
                    "rightValue": "newsletter"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": false,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.category }}",
                    "rightValue": "automated"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": false,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.category }}",
                    "rightValue": "spam_promo"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {
          "allMatchingOutputs": false
        }
      },
      "typeVersion": 3.2
    },
    {
      "id": "1b2f61c6-0e8e-41ed-85b1-7ede8ddf12db",
      "name": "\ud83c\udff7\ufe0f Label: Urgent",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1600,
        800
      ],
      "parameters": {
        "labelIds": [
          "YOUR_LABEL_ID_URGENT"
        ],
        "messageId": "={{ $json.gmailId }}",
        "operation": "addLabels"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "6698eeaa-a2fd-429e-b552-f09438535441",
      "name": "\ud83d\udcdd Draft Reply (Urgent)",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1840,
        800
      ],
      "parameters": {
        "message": "={{ $json.replyDraft }}",
        "options": {
          "threadId": "={{ $json.threadId }}"
        },
        "subject": "={{ $json.replySubject }}",
        "resource": "draft"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "d338bc75-a2b5-41bc-96a1-88aa96a1fc25",
      "name": "\ud83c\udff7\ufe0f Label: Action Required",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1600,
        960
      ],
      "parameters": {
        "labelIds": [
          "YOUR_LABEL_ID_ACTION_REQUIRED"
        ],
        "messageId": "={{ $json.gmailId }}",
        "operation": "addLabels"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "1540bf40-50a9-4cea-8255-27bf0aa0cccd",
      "name": "\ud83d\udcdd Draft Reply (Action)",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1840,
        960
      ],
      "parameters": {
        "message": "={{ $json.replyDraft }}",
        "options": {
          "threadId": "={{ $json.threadId }}"
        },
        "subject": "={{ $json.replySubject }}",
        "resource": "draft"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "ef532b54-ad93-4805-94a4-af104ef20588",
      "name": "\ud83c\udff7\ufe0f Label: Follow-up",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1600,
        1120
      ],
      "parameters": {
        "labelIds": [
          "YOUR_LABEL_ID_FOLLOW_UP"
        ],
        "messageId": "={{ $json.gmailId }}",
        "operation": "addLabels"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "c360cea5-623d-45eb-8ce0-c6b7c4db9deb",
      "name": "\ud83c\udff7\ufe0f Label: Newsletter",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1600,
        1248
      ],
      "parameters": {
        "labelIds": [
          "YOUR_LABEL_ID_NEWSLETTER"
        ],
        "messageId": "={{ $json.gmailId }}",
        "operation": "addLabels"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "4304e340-abe5-4b39-b3de-c07831ed52cf",
      "name": "\ud83c\udff7\ufe0f Label: Automated",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1600,
        1408
      ],
      "parameters": {
        "labelIds": [
          "YOUR_LABEL_ID_AUTOMATED"
        ],
        "messageId": "={{ $json.gmailId }}",
        "operation": "addLabels"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "139f1b21-d3bf-4e64-bb24-31e752bbde15",
      "name": "\ud83c\udff7\ufe0f Label: Spam-Promo",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1600,
        1600
      ],
      "parameters": {
        "labelIds": [
          "YOUR_LABEL_ID_SPAM_PROMO"
        ],
        "messageId": "={{ $json.gmailId }}",
        "operation": "addLabels"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "91a158e1-ccc9-4238-a69b-0418d60cb82d",
      "name": "\ud83d\udccb Prepare Log Entry",
      "type": "n8n-nodes-base.code",
      "position": [
        2080,
        1088
      ],
      "parameters": {
        "jsCode": "// Merge all paths back for logging\nconst item = $input.all()[0].json;\n\n// Try to get original classification data from the appropriate upstream node\nlet classificationData = {};\n\ntry {\n  classificationData = $('\ud83c\udfaf Extract Classification').item.json;\n} catch (e) {\n  classificationData = item;\n}\n\nreturn {\n  json: {\n    // For Google Sheets logging\n    Date: classificationData.date || new Date().toISOString(),\n    From: classificationData.from || '',\n    Subject: classificationData.subject || '',\n    Category: classificationData.category || '',\n    Priority: classificationData.priority || '',\n    Confidence: classificationData.confidence || '',\n    Reason: classificationData.reason || '',\n    Summary: classificationData.summary || '',\n    'Key Action': classificationData.keyAction || '',\n    'Has Reply Draft': classificationData.replyDraft ? 'Yes' : 'No',\n    Sentiment: classificationData.sentimentTone || '',\n    'Auto Archive': classificationData.shouldAutoArchive ? 'Yes' : 'No',\n    'Gmail ID': classificationData.gmailId || '',\n    'Processed At': classificationData.processedAt || new Date().toISOString()\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "3518b511-57c9-4d97-aa32-9b79631e8994",
      "name": "\ud83d\udcca Log to Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2384,
        1088
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [
            {
              "id": "Date",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "From",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "From",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Subject",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Subject",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Category",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Category",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Priority",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Priority",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Confidence",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Confidence",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Reason",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Reason",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Summary",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Summary",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Key Action",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Key Action",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Has Reply Draft",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Has Reply Draft",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Sentiment",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Sentiment",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Auto Archive",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Auto Archive",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Gmail ID",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Gmail ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Processed At",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Processed At",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 772918340,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit#gid=772918340",
          "cachedResultName": "log entry"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_GOOGLE_SHEET_ID",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit",
          "cachedResultName": "Email Triage Log"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.6
    },
    {
      "id": "b39e4a70-1ccc-421f-87d6-1ac95fe53c48",
      "name": "Ollama Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOllama",
      "position": [
        560,
        1328
      ],
      "parameters": {
        "model": "mistral",
        "options": {}
      },
      "credentials": {
        "ollamaApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "00000000-0000-0000-0000-000000000000",
  "connections": {
    "Ollama Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "\ud83e\udd16 AI Email Classifier",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udce5 Gmail Trigger": {
      "main": [
        [
          {
            "node": "\ud83d\udd27 Extract Email Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83c\udff7\ufe0f Label: Urgent": {
      "main": [
        [
          {
            "node": "\ud83d\udcdd Draft Reply (Urgent)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udccb Prepare Log Entry": {
      "main": [
        [
          {
            "node": "\ud83d\udcca Log to Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udd00 Route by Category": {
      "main": [
        [
          {
            "node": "\ud83c\udff7\ufe0f Label: Urgent",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "\ud83c\udff7\ufe0f Label: Action Required",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "\ud83c\udff7\ufe0f Label: Follow-up",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "\ud83c\udff7\ufe0f Label: Newsletter",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "\ud83c\udff7\ufe0f Label: Automated",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "\ud83c\udff7\ufe0f Label: Spam-Promo",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udd27 Extract Email Data": {
      "main": [
        [
          {
            "node": "\ud83e\udd16 AI Email Classifier",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83c\udff7\ufe0f Label: Automated": {
      "main": [
        [
          {
            "node": "\ud83d\udccb Prepare Log Entry",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83c\udff7\ufe0f Label: Follow-up": {
      "main": [
        [
          {
            "node": "\ud83d\udccb Prepare Log Entry",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83e\udd16 AI Email Classifier": {
      "main": [
        [
          {
            "node": "\ud83c\udfaf Extract Classification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83c\udff7\ufe0f Label: Newsletter": {
      "main": [
        [
          {
            "node": "\ud83d\udccb Prepare Log Entry",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83c\udff7\ufe0f Label: Spam-Promo": {
      "main": [
        [
          {
            "node": "\ud83d\udccb Prepare Log Entry",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udcdd Draft Reply (Action)": {
      "main": [
        [
          {
            "node": "\ud83d\udccb Prepare Log Entry",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udcdd Draft Reply (Urgent)": {
      "main": [
        [
          {
            "node": "\ud83d\udccb Prepare Log Entry",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83c\udfaf Extract Classification": {
      "main": [
        [
          {
            "node": "\ud83d\udd00 Route by Category",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83c\udff7\ufe0f Label: Action Required": {
      "main": [
        [
          {
            "node": "\ud83d\udcdd Draft Reply (Action)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}