{
  "id": "A59lcfVE7yoNfOYa",
  "name": "AI Email Reply Management with Reply and Unsubscribe Handling",
  "tags": [],
  "nodes": [
    {
      "id": "c9e9741c-5d9f-4302-adc4-3447124d1098",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        976,
        -320
      ],
      "parameters": {
        "width": 480,
        "height": 896,
        "content": "## AI Email Reply Management with Suppression and Unsubscribe Handling\n\n### How it works\n\nThis workflow monitors an IMAP inbox for reply emails, filters for reply subjects, and structures the email content for downstream processing. A separate AI-processing path prepares a prompt, sends it to OpenAI, normalizes the model response, and routes the result through a switch. Depending on the AI classification, it replies in Gmail, records the event, updates suppression or unsubscribe handling, and adjusts Gmail thread labels.\n\n### Setup steps\n\n- Configure the IMAP email trigger with the mailbox credentials and folder to monitor.\n- Connect OpenAI credentials and verify the prompt/code nodes produce the expected JSON fields for the model request and response parsing.\n- Configure Gmail OAuth credentials for thread lookup, reply sending, and label add/remove operations.\n- Update the suppression-list HTTP request with the real ESP API host, authentication headers, and request body fields.\n- Create or select the n8n Data Table columns used by the Insert row nodes for logging suppression and unsubscribe actions.\n- Set the Gmail label IDs/names used to mark processed threads and remove any inbox or workflow labels as intended.\n\n### Customization\n\nAdjust the subject filter, AI prompt, switch conditions, reply templates, data-table schema, and Gmail label behavior to match the organization\u2019s reply categories and compliance process."
      },
      "typeVersion": 1
    },
    {
      "id": "aa65e2f8-68c2-4b41-ba88-98ddb1f8b407",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1536,
        80
      ],
      "parameters": {
        "color": 7,
        "width": 704,
        "height": 320,
        "content": "## Receive reply emails\n\nWatches the inbox for new email, keeps messages whose subject indicates a reply, and structures the email content for later classification. This is a distinct input cluster on the far left of the canvas."
      },
      "typeVersion": 1
    },
    {
      "id": "63bc2c2d-7d8e-4257-a261-cacf3fe84e5e",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2272,
        80
      ],
      "parameters": {
        "color": 7,
        "width": 880,
        "height": 320,
        "content": "## Prepare AI request\n\nBuilds the AI prompt, stores execution data, and sends the prepared message to the OpenAI model. These nodes form the central AI request cluster."
      },
      "typeVersion": 1
    },
    {
      "id": "e45c1382-a855-4133-9049-1b73166240be",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3344,
        144
      ],
      "parameters": {
        "color": 7,
        "width": 400,
        "height": 320,
        "content": "## Route AI decision\n\nParses and normalizes the model\u2019s JSON response, then uses a switch to branch into the appropriate reply-management path."
      },
      "typeVersion": 1
    },
    {
      "id": "8aeb7e79-743c-4bae-9a9f-8bfbbfc58391",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3856,
        0
      ],
      "parameters": {
        "color": 7,
        "width": 848,
        "height": 320,
        "content": "## Handle suppression reply\n\nRetrieves the Gmail thread, sends the Unsubscription confirmation reply, posts the contact to the ESP suppression list, and logs the action in a data table. This is the upper branch immediately after the AI routing switch."
      },
      "typeVersion": 1
    },
    {
      "id": "fd8d6615-2859-458a-aab4-624af7b9baab",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4752,
        0
      ],
      "parameters": {
        "color": 7,
        "width": 400,
        "height": 320,
        "content": "## Finalize Unsubscription thread\n\nApplies and removes Gmail labels on the processed suppression thread to mark it as handled and clean up its workflow status."
      },
      "typeVersion": 1
    },
    {
      "id": "5672d3cb-4c32-423b-85d0-2653d98d7da2",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3904,
        368
      ],
      "parameters": {
        "color": 7,
        "width": 608,
        "height": 320,
        "content": "## Handle reply\n\nRetrieves the Gmail thread for the second AI branch, sends the configured reply"
      },
      "typeVersion": 1
    },
    {
      "id": "c796a67b-6db7-47fb-8e3b-b185beb647e1",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4576,
        368
      ],
      "parameters": {
        "color": 7,
        "width": 448,
        "height": 320,
        "content": "## Keep the email in reply thread\n\nUpdates Gmail labels on the processed thread, marking it handled and removing the prior workflow label."
      },
      "typeVersion": 1
    },
    {
      "id": "6d1c62a9-f37e-4392-b0fe-2f51cce41a15",
      "name": "When Email Received (IMAP)",
      "type": "n8n-nodes-base.emailReadImap",
      "position": [
        1600,
        224
      ],
      "parameters": {
        "options": {
          "customEmailConfig": "[   \"UNSEEN\",   [\"OR\",     [\"HEADER\",\"In-Reply-To\",\"<\"],     [\"HEADER\",\"References\",\"<\"]   ] ]",
          "trackLastMessageId": true
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "bf02c17a-5b05-4423-a334-d9a494628fc3",
      "name": "OpenAI Model Interaction",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        2800,
        224
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5.2",
          "cachedResultName": "GPT-5.2"
        },
        "options": {
          "temperature": 0.4
        },
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "=You are YourCompany\u2019s email triage assistant. Write short, human-sounding replies.\n\nReturn ONLY valid JSON with exactly these fields:\n{\n  \"sentimentLabel\": \"positive\" | \"negative\",\n  \"sentimentPercent\": number,\n  \"unsubscribe\": boolean,\n  \"responseText\": string,\n  \"reasoning\": string\n}\n\nHard rules:\n- Output JSON only. No markdown. No extra fields.\n- responseText must be email BODY only (no subject/headers/signature).\n- Greeting required: \"Hello {name},\" using senderName; if missing, use \"there\".\n- Normal replies: 2\u20134 sentences, plainspoken and natural.\n- Unsubscribe replies: 1\u20132 sentences confirming removal. No persuasion.\n- No legal/medical advice, no outcome promises, no marketing fluff.\n\nBehavior:\nA) If they ask to stop/unsubscribe/remove: set unsubscribe=true and confirm removal. Do NOT include a phone CTA.\nB) If they say \"not interested\"/\"wrong benefit\": acknowledge and close politely. Do NOT pitch. Do NOT include the phone number unless they explicitly ask for our service help.\nC) If it\u2019s small talk/compliment/joke: reply politely in 1 sentence. No phone number.\nD) If they\u2019re confused/asking for help/details: give next step + offer \u201creply here\u201d option. You may include the phone number ONCE, naturally.\n\nPhone number rule:\n- Only include YOUR_PHONE_NUMBER when it\u2019s actually relevant (they asked for help/details). Include it only once.\n\nLocation:\n- Only mention their state/location if they mentioned it. No guarantees.\n\nreasoning:\n- 1 short sentence describing detected intent and why you chose the reply style."
            },
            {
              "content": "={\n  \"objective\": \"Triage the sender\u2019s last message and reply in a natural, helpful tone. Only include the phone number if the sender is asking for help with our service or details. If the sender asks to unsubscribe or says they are not interested, do not persuade and do not include the phone number.\",\n  \"trigger_words_hint\": [\"call\",\"phone\",\"next steps\",\"issue\",\"follow-up\",\"help\",\"not interested\",\"unsubscribe\",\"stop\",\"remove\",\"do not email\",\"do not text\",\"wrong benefit\"],\n  \"email\": {\n    \"subject\": \"{{$json.subject}}\",\n    \"reply\": \"{{$json.reply}}\",\n    \"senderEmail\": \"{{$json.senderEmail}}\",\n    \"senderName\": \"{{$json.senderName}}\",\n    \"date\": \"{{$json.date}}\",\n    \"messageId\": \"{{$json.messageId}}\",\n    \"inReplyTo\": \"{{$json.inReplyTo}}\",\n    \"to\": \"{{$json.senderEmail}}\",\n    \"fromName\": \"{{$json.fromName}}\"\n  }\n}"
            }
          ]
        },
        "simplify": "={\"type\":\"json_object\"}"
      },
      "executeOnce": false,
      "typeVersion": 1.8
    },
    {
      "id": "b4234acd-651d-4e0c-a785-f1523306e3b7",
      "name": "Parse Email Content",
      "type": "n8n-nodes-base.code",
      "position": [
        2080,
        224
      ],
      "parameters": {
        "jsCode": "/**\n * Input: items from IMAP Email node (one per email)\n * Output: [{\n *   subject, reply, senderEmail, senderName, date, messageId, inReplyTo, references, to,\n *   originalTo,\n *   replySubject, replyToEmail, threadInReplyTo, threadReferences,\n *   fromName, fromEmail, displayFrom   // <-- use these when sending\n * }]\n */\n\n// adjust to your domain & known mailboxes (used to pick the correct identity from \"To:\")\nconst OUR_DOMAIN  = \"mail.yourcompany.com\";\nconst OUR_EMAILS  = [\n  \"user@example.com\",\n  \"user@example.com\"\n];\n\nfunction htmlToText(html = \"\") {\n  let txt = html.replace(/<style[\\s\\S]*?<\\/style>/gi, \"\")\n                .replace(/<script[\\s\\S]*?<\\/script>/gi, \"\")\n                .replace(/<br\\s*\\/?>/gi, \"\\n\")\n                .replace(/<\\/p>/gi, \"\\n\")\n                .replace(/<[^>]+>/g, \" \");\n  const entities = { \"&amp;\": \"&\", \"&lt\": \"<\", \"&gt\": \">\", \"&quot\": \"\\\"\", \"&#39\": \"'\" };\n  txt = txt.replace(/&(amp|lt|gt|quot|#39);/g, m => entities[m] || m);\n  return txt.replace(/\\s+\\n/g, \"\\n\").replace(/\\n{3,}/g, \"\\n\\n\").trim();\n}\n\nfunction stripQuoted(text = \"\") {\n  const lines = text.split(/\\r?\\n/);\n  const out = [];\n  for (const line of lines) {\n    if (/^\\s*>/.test(line)) break;\n    if (/^On .+wrote:$/i.test(line.trim())) break;\n    if (/^From:\\s.+/i.test(line.trim())) break;\n    out.push(line);\n  }\n  return out.join(\"\\n\").trim();\n}\n\nfunction normalizeText(s = \"\") {\n  return (s || \"\")\n    .replace(/\\u00A0/g, \" \")\n    .replace(/\u00c2/g, \"\")\n    .replace(/[\u201c\u201d]/g, '\"')\n    .replace(/[\u2018\u2019]/g, \"'\")\n    .replace(/\\s+\\n/g, \"\\n\")\n    .replace(/\\n{3,}/g, \"\\n\\n\")\n    .trim();\n}\n\nfunction parseFrom(from = \"\") {\n  let name, email;\n  const angle = from.match(/^(.*?)\\s*<([^>]+)>/);\n  if (angle) {\n    name = angle[1]?.replace(/^\"|\"$/g, \"\").trim();\n    email = angle[2].trim();\n  } else {\n    const em = from.match(/[\\w.+-]+@[\\w.-]+\\.[A-Za-z]{2,}/);\n    email = em ? em[0] : \"\";\n    name = from.replace(email, \"\").replace(/[<>\"']/g, \"\").trim() || undefined;\n  }\n  return { senderName: name || undefined, senderEmail: email || \"\" };\n}\n\n// parse a comma-separated list of addresses into [{name,email}]\nfunction parseAddressList(list = \"\") {\n  const out = [];\n  const re = /(?:\"?([^\"<]+)\"?\\s*)?<\\s*([^>]+)\\s*>|([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,})/g;\n  let m;\n  while ((m = re.exec(list))) {\n    if (m[2]) out.push({ name: (m[1] || \"\").trim() || undefined, email: m[2].trim() });\n    else if (m[3]) out.push({ name: undefined, email: m[3].trim() });\n  }\n  return out;\n}\n\nfunction getHeader(j, name) {\n  const h = j[name] || j?.headers?.[name] || j?.metadata?.[name];\n  return typeof h === \"string\" ? h.trim() : \"\";\n}\n\nfunction subjectForReply(subj = \"\") {\n  const s = subj.trim();\n  return /^re:/i.test(s) ? s : (s ? `Re: ${s}` : \"Re:\");\n}\n\nreturn items.map(item => {\n  const j = item.json || {};\n\n  const subject = j.subject || j?.headers?.subject || \"\";\n  const from    = j.from    || j?.headers?.from    || \"\";\n  const to      = j.to      || j?.headers?.to      || \"\";\n\n  // --- who wrote to us (the lead) ---\n  const { senderName, senderEmail } = parseFrom(from);\n\n  // --- who they wrote to (the campaign identity) ---\n  const toList = parseAddressList(to);\n  const ourTo =\n    toList.find(a => OUR_EMAILS.includes((a.email || \"\").toLowerCase())) ||\n    toList.find(a => (a.email || \"\").toLowerCase().endsWith(\"@\"+OUR_DOMAIN)) ||\n    toList[0] || { name: undefined, email: \"\" };\n\n  // dynamic campaign sender name/email for outgoing reply\n  const fromName  = ourTo.name || undefined; // <-- the display name you used in the campaign\n  const fromEmail = ourTo.email || \"user@example.com\";\n  const displayFrom = fromName ? `${fromName} <${fromEmail}>` : fromEmail;\n\n  // Prefer textPlain; fallback to HTML\u2192text\n  const plain = j.textPlain || j.text || \"\";\n  const html  = j.textHtml  || \"\";\n  const bodyText = plain ? plain : htmlToText(html);\n\n  // Clean & de-quote for the fresh reply content\n  const reply = normalizeText(stripQuoted(bodyText));\n\n  // Threading headers from the incoming message\n  const messageId  = getHeader(j, \"message-id\");   // e.g. \"<abc@domain>\"\n  const inReplyTo  = getHeader(j, \"in-reply-to\");\n  const references = getHeader(j, \"references\");\n\n  // Original recipient (for routing/analytics)\n  const originalTo =\n    getHeader(j, \"x-gm-original-to\") ||\n    getHeader(j, \"delivered-to\")     ||\n    getHeader(j, \"x-original-to\")    ||\n    to;\n\n  // Build send-ready fields to keep the reply in the same thread\n  const replySubject     = subjectForReply(subject);\n  const replyToEmail     = senderEmail || \"\";\n  const threadInReplyTo  = messageId || inReplyTo || \"\";\n  const threadReferences = [references, messageId].filter(Boolean).join(\" \").trim();\n\n  return {\n    json: {\n      subject,\n      reply,\n      senderEmail,\n      senderName,\n      date: j.date || j?.headers?.date || \"\",\n      messageId,\n      inReplyTo,\n      references,\n      to,\n      originalTo,\n      // ready for send step\n      replySubject,\n      replyToEmail,\n      threadInReplyTo,\n      threadReferences,\n      // dynamic campaign identity to use when sending\n      fromName,\n      fromEmail,\n      displayFrom\n    }\n  };\n});\n"
      },
      "typeVersion": 2
    },
    {
      "id": "b402edb3-e5fe-435b-9a8d-cefd1dbfcaf0",
      "name": "Prepare AI Prompt",
      "type": "n8n-nodes-base.code",
      "position": [
        2320,
        224
      ],
      "parameters": {
        "jsCode": "/**\n * Input items must have:\n * subject, reply, senderEmail, senderName, date, messageId, inReplyTo, to\n * Add your own trigger words in TRIGGER_WORDS; these are hints, not hard filters.\n */\nconst TRIGGER_WORDS = [\n  \"call\", \"phone\", \"when will I hear\", \"next steps\", \"issue\", \"follow-up\",\n  \"help\", \"question\", \"ok\", \"okay\", \"not interested\", \"unsubscribe\", \"stop\"\n];\n\nfunction safe(s) {\n  return (s || \"\").toString().trim();\n}\n\nreturn items.map(({ json }) => {\n  const email = {\n    subject: safe(json.subject),\n    reply: safe(json.reply),\n    senderEmail: safe(json.senderEmail),\n    senderName: safe(json.senderName),\n    date: safe(json.date),\n    messageId: safe(json.messageId),\n    inReplyTo: safe(json.inReplyTo),\n    to: safe(json.to),\n  };\n\n  const systemMessage = `\nYou are a careful, empathic triage assistant for YourCompany. Your job:\n1) Understand the *intent and tone* of a short email reply.\n2) Draft a concise, human, conversational response that encourages the recipient to **call YOUR_PHONE_NUMBER** to proceed.\n3) Assess whether the reply is **positive** or **negative** and provide a confidence percent.\n4) If the user expresses opt-out intent (e.g., \"unsubscribe\", \"stop\", \"remove me\"), set unsubscribe=true and write a short confirmation **without** marketing or persuasion.\n\nConstraints:\n- Be respectful and plainspoken; no legal or medical advice.\n- Keep the CTA single and clear: \u201cPlease call us at YOUR_PHONE_NUMBER\u2026\u201d.\n- If the message is strongly negative or clearly an opt-out, do NOT try to persuade; acknowledge and confirm removal.\n- If location/state is mentioned, you may reassure we can assist there (if appropriate) but don\u2019t promise outcomes.\n- Output **valid JSON only**, matching the schema exactly.\n`;\n\n  const userMessage = {\n    objective: \"Encourage the user to call YOUR_PHONE_NUMBER while staying helpful and natural.\",\n    trigger_words_hint: TRIGGER_WORDS,\n    email\n  };\n\n  // Strict JSON schema instructions to reduce hallucinations:\n  const schema = `\nReturn JSON with exactly these fields:\n{\n  \"sentimentLabel\": \"positive\" | \"negative\",\n  \"sentimentPercent\": number,      // 0..100 integer preferred\n  \"unsubscribe\": boolean,\n  \"responseText\": string,          // the email reply to send\n  \"reasoning\": string              // brief rationale (1-2 sentences)\n}\nNo extra fields. No markdown. No explanations outside JSON.\n`;\n\n  return {\n    json: {\n      ...json,\n      _aiSystemMessage: systemMessage.trim(),\n      _aiUserMessage: JSON.stringify(userMessage),\n      _aiSchema: schema.trim()\n    }\n  };\n});\n"
      },
      "typeVersion": 2
    },
    {
      "id": "ed390bfb-2a3c-43d6-88c6-d46917d4a7ff",
      "name": "Normalize AI Response",
      "type": "n8n-nodes-base.code",
      "position": [
        3392,
        304
      ],
      "parameters": {
        "jsCode": "// Parse OpenAI's JSON (string in $.message.content) and MERGE with earlier fields,\n// then enforce a formal, line-broken email structure.\n\nconst USE_HTML_CTA = false; // set true if your send step uses HTML\nconst CTA_PLAIN = \"For any support related to our service, please feel free to reach us at YOUR_PHONE_NUMBER (Mon\u2013Fri, 9am\u20136pm).\";\nconst CTA_HTML  = 'Please call us at <a href=\"tel:YOUR_PHONE_NUMBER\">YOUR_PHONE_NUMBER</a> (Mon\u2013Fri, 9am\u20136pm).';\nconst CTA_LINE  = USE_HTML_CTA ? CTA_HTML : CTA_PLAIN;\n\nfunction firstName(s) {\n  const t = (s || \"\").trim();\n  if (!t) return \"there\";\n  const f = t.split(/\\s+/)[0];\n  return f.replace(/[^A-Za-z'-]/g, \"\") || \"there\";\n}\nfunction ensurePeriod(s = \"\") {\n  s = s.trim();\n  if (!s) return s;\n  return /[.!?]$/.test(s) ? s : s + \".\";\n}\nfunction normalizeSpace(s = \"\") {\n  return String(s)\n    .replace(/\\r\\n/g, \"\\n\")\n    .replace(/\\t/g, \" \")\n    .replace(/[ \\u00A0]+/g, \" \")\n    .replace(/\\n{3,}/g, \"\\n\\n\")\n    .trim();\n}\nfunction capWords(s = \"\", limit = 120) {\n  const words = s.split(/\\s+/);\n  return words.length <= limit ? s : words.slice(0, limit).join(\" \") + \"\u2026\";\n}\n\nreturn items.map(item => {\n  const j = item.json || {};\n  const raw = j.message?.content ?? j.content ?? j.text ?? \"{}\";\n\n  let parsed;\n  try {\n    parsed = typeof raw === \"string\" ? JSON.parse(raw) : raw;\n  } catch {\n    parsed = {\n      sentimentLabel: \"positive\",\n      sentimentPercent: 60,\n      unsubscribe: false,\n      responseText: \"Thanks for your note. We can help discuss next steps.\",\n      reasoning: \"Fallback: invalid JSON from model.\"\n    };\n  }\n\n  // Coerce types\n  const pct = Number.isFinite(Number(parsed.sentimentPercent))\n    ? Math.max(0, Math.min(100, Math.round(Number(parsed.sentimentPercent))))\n    : 50;\n\n  const name = firstName(j.senderName);\n  const fromName = j.fromName || \"Your Company\";\n\n  // Build middle section\n  let middle = normalizeSpace(parsed.responseText || \"\");\n\n  // Unsubscribe: force a short, neutral confirmation; no persuasion\n  if (parsed.unsubscribe) {\n    middle = \"Your request to be removed has been processed. We will not email you again.\";\n  }\n\n  // Sentence handling\n  let sentences = middle\n    .split(/(?<=\\.)\\s+|(?<=\\?)\\s+|(?<=!)\\s+/)\n    .map(s => s.trim())\n    .filter(Boolean)\n    .map(ensurePeriod);\n\n  // Word cap for normal case\n  if (!parsed.unsubscribe) {\n    const joined = capWords(sentences.join(\" \"), 120);\n    sentences = joined.split(/(?<=\\.)\\s+|(?<=\\?)\\s+|(?<=!)\\s+/).filter(Boolean);\n  } else {\n    // Keep 2\u20133 sentences max for unsubscribe path\n    sentences = sentences.slice(0, 3);\n  }\n\n  // Ensure CTA appears exactly once (remove any prior, then append once)\n  sentences = sentences.filter(s => !s.includes(\"YOUR_PHONE_NUMBER\"));\n  sentences.push(CTA_LINE);\n\n  const formatted = [\n   \n    \"\",\n    sentences.join(\" \"),\n    \"\",\n    \"Best regards,\",\n    fromName,\n    \"Customer Support\"\n  ].join(\"\\n\");\n\n  return {\n    json: {\n      ...j,\n      sentimentLabel: parsed.sentimentLabel === \"positive\" ? \"positive\" : \"negative\",\n      sentimentPercent: pct,\n      unsubscribe: !!parsed.unsubscribe,\n      responseText: formatted,       // final, formal body (plain text with clean line breaks)\n      reasoning: normalizeSpace(parsed.reasoning || \"\")\n    }\n  };\n});\n"
      },
      "typeVersion": 2
    },
    {
      "id": "d55a4093-1162-4c68-8a25-09f0bebf7a81",
      "name": "Route by Email Status",
      "type": "n8n-nodes-base.switch",
      "position": [
        3600,
        304
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "unsubscribe",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "632f5519-db63-4ac9-85b8-4b2afbafd6b9",
                    "operator": {
                      "type": "boolean",
                      "operation": "true",
                      "singleValue": true
                    },
                    "leftValue": "={{ $json.unsubscribe }}",
                    "rightValue": ""
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "normal reply",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "35b6d983-cea7-4151-9448-1b037e972350",
                    "operator": {
                      "type": "boolean",
                      "operation": "false",
                      "singleValue": true
                    },
                    "leftValue": "={{ $json.unsubscribe }}",
                    "rightValue": ""
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.3
    },
    {
      "id": "8c8f4e81-d797-4e36-9d2a-496b878a8a72",
      "name": "Reply to Email - Thread ID 1",
      "type": "n8n-nodes-base.gmail",
      "position": [
        4112,
        160
      ],
      "parameters": {
        "message": "={{ $('Normalize AI Response').item.json.responseText }}",
        "options": {
          "senderName": "={{ $('Parse Email Content').item.json.fromName }}"
        },
        "emailType": "text",
        "messageId": "={{ $json.id }}",
        "operation": "reply"
      },
      "typeVersion": 2.1
    },
    {
      "id": "0ba170d5-2f6f-453d-aa8a-8dae4a8f31dd",
      "name": "Reply to Email - Thread ID 2",
      "type": "n8n-nodes-base.gmail",
      "position": [
        4176,
        528
      ],
      "parameters": {
        "message": "={{ $('Normalize AI Response').item.json.responseText }}",
        "options": {
          "senderName": "={{ ($('Parse Email Content').item.json.fromName ?? '').trim() ? $('Parse Email Content').item.json.fromName : 'Your Company' }}"
        },
        "emailType": "text",
        "messageId": "={{ $json.id }}",
        "operation": "reply"
      },
      "typeVersion": 2.1
    },
    {
      "id": "37db13c5-00d7-47cd-b9fd-33fffac35459",
      "name": "Post to Suppression List API",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        4320,
        160
      ],
      "parameters": {
        "url": "=https://your-esp-api-host.com/v2/Api/SuppressionLists/1?apiKey=YOUR_ESP_API_KEY&entry={{ encodeURIComponent($('Parse Email Content').item.json.senderEmail || $json.replyToEmail || $json.senderEmail) }}\n",
        "method": "POST",
        "options": {},
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "7e53d7c6-7aff-44a0-977a-a6675d8cf5c4",
      "name": "Insert Data to Table 1",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        4560,
        160
      ],
      "parameters": {
        "columns": {
          "value": {
            "user": "={{ $('Parse Email Content').item.json.senderEmail }}",
            "reply": "={{ $('Parse Email Content').item.json.reply }}",
            "action": "={{ $('Normalize AI Response').item.json.unsubscribe ? 'unsubscribe' : 'reply' }}",
            "sender": "={{ $('Parse Email Content').item.json.fromName }}",
            "Product": "our service",
            "subject": "={{ ( $('Parse Email Content').item.json.subject || '' ).replace(/^(?:\\s*re:\\s*)+/i, '') }}",
            "confidence": "={{ $('Normalize AI Response').item.json.sentimentPercent }}",
            "ai_response": "={{ $('Normalize AI Response').item.json.responseText }}",
            "ai_reasoning": "={{ $('Normalize AI Response').item.json.reasoning }}"
          },
          "schema": [
            {
              "id": "user",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "user",
              "defaultMatch": false
            },
            {
              "id": "reply",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "reply",
              "defaultMatch": false
            },
            {
              "id": "ai_response",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "ai_response",
              "defaultMatch": false
            },
            {
              "id": "action",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "action",
              "defaultMatch": false
            },
            {
              "id": "confidence",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "confidence",
              "defaultMatch": false
            },
            {
              "id": "sender",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "sender",
              "defaultMatch": false
            },
            {
              "id": "subject",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "subject",
              "defaultMatch": false
            },
            {
              "id": "ai_reasoning",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "ai_reasoning",
              "defaultMatch": false
            },
            {
              "id": "Product",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Product",
              "defaultMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "DVwhGH9Clg6sZM9b",
          "cachedResultUrl": "/projects/3uauwbRmrIfAX6rx/datatables/DVwhGH9Clg6sZM9b",
          "cachedResultName": "Email reply management"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "7a430594-99df-4662-ab4f-93a6e7181bfc",
      "name": "Insert Data to Table 2",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        4368,
        528
      ],
      "parameters": {
        "columns": {
          "value": {
            "user": "={{ $('Parse Email Content').item.json.senderEmail }}",
            "reply": "={{ $('Parse Email Content').item.json.reply }}",
            "action": "={{ $('Normalize AI Response').item.json.unsubscribe ? 'unsubscribe' : 'reply' }}",
            "sender": "={{ $('Parse Email Content').item.json.fromName }}",
            "Product": "our service",
            "subject": "={{ ( $('Parse Email Content').item.json.subject || '' ).replace(/^(?:\\s*re:\\s*)+/i, '') }}",
            "confidence": "={{ $('Normalize AI Response').item.json.sentimentPercent }}",
            "ai_response": "={{ $('Normalize AI Response').item.json.responseText }}",
            "ai_reasoning": "={{ $('Normalize AI Response').item.json.reasoning }}"
          },
          "schema": [
            {
              "id": "user",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "user",
              "defaultMatch": false
            },
            {
              "id": "reply",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "reply",
              "defaultMatch": false
            },
            {
              "id": "ai_response",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "ai_response",
              "defaultMatch": false
            },
            {
              "id": "action",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "action",
              "defaultMatch": false
            },
            {
              "id": "confidence",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "confidence",
              "defaultMatch": false
            },
            {
              "id": "sender",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "sender",
              "defaultMatch": false
            },
            {
              "id": "subject",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "subject",
              "defaultMatch": false
            },
            {
              "id": "ai_reasoning",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "ai_reasoning",
              "defaultMatch": false
            },
            {
              "id": "Product",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Product",
              "defaultMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "DVwhGH9Clg6sZM9b",
          "cachedResultUrl": "/projects/3uauwbRmrIfAX6rx/datatables/DVwhGH9Clg6sZM9b",
          "cachedResultName": "Email reply mgmt user@example.com"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "2d1b3be7-78f0-4af8-9014-493f53fb7aca",
      "name": "Filter Replied Emails",
      "type": "n8n-nodes-base.filter",
      "position": [
        1840,
        224
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "76f6e632-e05f-4d25-883c-241f95f0957f",
              "operator": {
                "type": "string",
                "operation": "contains"
              },
              "leftValue": "={{ $json.subject }}",
              "rightValue": "Re: "
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "6e967377-ca70-46bf-9085-be2a2e27e393",
      "name": "Add Label to Email Thread 1",
      "type": "n8n-nodes-base.gmail",
      "position": [
        4800,
        160
      ],
      "parameters": {
        "labelIds": [
          "Label_625804802492984078"
        ],
        "resource": "thread",
        "threadId": "={{ $('Fetch Email Thread ID 1').item.json.id }}",
        "operation": "addLabels"
      },
      "typeVersion": 2.1
    },
    {
      "id": "78183de2-db0c-4781-adb4-7830da450d06",
      "name": "Remove Label from Thread 1",
      "type": "n8n-nodes-base.gmail",
      "position": [
        5008,
        160
      ],
      "parameters": {
        "labelIds": [
          "INBOX"
        ],
        "resource": "thread",
        "threadId": "={{ $('Fetch Email Thread ID 1').item.json.id }}",
        "operation": "removeLabels"
      },
      "typeVersion": 2.1
    },
    {
      "id": "2b8b3c7a-1c4f-467c-897e-00770a7112ee",
      "name": "Fetch Email Thread ID 1",
      "type": "n8n-nodes-base.gmail",
      "position": [
        3904,
        160
      ],
      "parameters": {
        "limit": 1,
        "filters": {
          "q": "=rfc822msgid:\"{{ $('Prepare AI Prompt').item.json.messageId }}\""
        },
        "resource": "thread",
        "authentication": "serviceAccount"
      },
      "credentials": {
        "googleApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "a95b61ad-8c61-4acd-9d35-5b695f8bba91",
      "name": "Add Label to Email Thread 2",
      "type": "n8n-nodes-base.gmail",
      "position": [
        4624,
        528
      ],
      "parameters": {
        "labelIds": [
          "Label_3840368677770173027"
        ],
        "resource": "thread",
        "threadId": "={{ $('Fetch Email Thread ID 2').item.json.id }}",
        "operation": "addLabels"
      },
      "typeVersion": 2.1
    },
    {
      "id": "bf433fbd-24ea-4674-ba9f-3203d629b634",
      "name": "Remove Label from Thread 2",
      "type": "n8n-nodes-base.gmail",
      "position": [
        4880,
        528
      ],
      "parameters": {
        "labelIds": [
          "INBOX"
        ],
        "resource": "thread",
        "threadId": "={{ $json.messages[0].id }}",
        "operation": "removeLabels"
      },
      "typeVersion": 2.1
    },
    {
      "id": "4dc51705-973a-496b-bafb-c3a9cb3f0af3",
      "name": "Fetch Email Thread ID 2",
      "type": "n8n-nodes-base.gmail",
      "position": [
        3952,
        528
      ],
      "parameters": {
        "limit": 1,
        "filters": {
          "q": "=rfc822msgid:\"{{ $('Prepare AI Prompt').item.json.messageId }}\""
        },
        "resource": "thread"
      },
      "typeVersion": 2.1
    },
    {
      "id": "13eea39f-8525-41d5-b206-0d24db66ec27",
      "name": "Transfer Execution Data",
      "type": "n8n-nodes-base.executionData",
      "position": [
        2560,
        224
      ],
      "parameters": {
        "dataToSave": {
          "values": [
            {
              "key": "email",
              "value": "={{ $json.replyToEmail }}"
            }
          ]
        }
      },
      "typeVersion": 1.1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "4edd9d1f-1739-4b58-a414-77cbd3420dc2",
  "connections": {
    "Prepare AI Prompt": {
      "main": [
        [
          {
            "node": "Transfer Execution Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Email Content": {
      "main": [
        [
          {
            "node": "Prepare AI Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Replied Emails": {
      "main": [
        [
          {
            "node": "Parse Email Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize AI Response": {
      "main": [
        [
          {
            "node": "Route by Email Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by Email Status": {
      "main": [
        [
          {
            "node": "Fetch Email Thread ID 1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Fetch Email Thread ID 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Insert Data to Table 1": {
      "main": [
        [
          {
            "node": "Add Label to Email Thread 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Insert Data to Table 2": {
      "main": [
        [
          {
            "node": "Add Label to Email Thread 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Email Thread ID 1": {
      "main": [
        [
          {
            "node": "Reply to Email - Thread ID 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Email Thread ID 2": {
      "main": [
        [
          {
            "node": "Reply to Email - Thread ID 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Transfer Execution Data": {
      "main": [
        [
          {
            "node": "OpenAI Model Interaction",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Model Interaction": {
      "main": [
        [
          {
            "node": "Normalize AI Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When Email Received (IMAP)": {
      "main": [
        [
          {
            "node": "Filter Replied Emails",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add Label to Email Thread 1": {
      "main": [
        [
          {
            "node": "Remove Label from Thread 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add Label to Email Thread 2": {
      "main": [
        [
          {
            "node": "Remove Label from Thread 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Post to Suppression List API": {
      "main": [
        [
          {
            "node": "Insert Data to Table 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Reply to Email - Thread ID 1": {
      "main": [
        [
          {
            "node": "Post to Suppression List API",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Reply to Email - Thread ID 2": {
      "main": [
        [
          {
            "node": "Insert Data to Table 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}