{
  "name": "NTF 03: Email Triage Agent",
  "tags": [
    {
      "name": "NTF-Playbook"
    }
  ],
  "settings": {
    "executionOrder": "v1"
  },
  "nodes": [
    {
      "id": "sticky-readme",
      "name": "README",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -60,
        -520
      ],
      "parameters": {
        "content": "## NTF 03: Email Triage Agent\n\n**Trigger:** Gmail polling every 5 minutes (unread, inbox)\n\n**Flow:**\n1. Poll Gmail for unread emails\n2. Extract sender, subject, body\n3. Claude classifies category and drafts reply\n4. Create Gmail draft (ready to review)\n5. If priority is high, also send a Slack ping\n\n**Setup:**\n- Connect Gmail OAuth credential (read + draft + modify scopes only)\n- Set your Slack channel id for urgent alerts\n- Edit the Claude prompt categories to match your inbox types\n- Note: this template does not auto-mark emails as read or apply a label. Add an `addLabel` step downstream if you want once-only processing.",
        "height": 380,
        "width": 540,
        "color": 6
      }
    },
    {
      "id": "gmail-trigger",
      "name": "Gmail Poll Unread",
      "type": "n8n-nodes-base.gmailTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        0
      ],
      "parameters": {
        "pollTimes": {
          "item": [
            {
              "mode": "everyX",
              "value": 5,
              "unit": "minutes"
            }
          ]
        },
        "filters": {
          "readStatus": "unread",
          "includeSpamTrash": false
        }
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "extract-email",
      "name": "Extract Email Fields",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        220,
        0
      ],
      "parameters": {
        "mode": "manual",
        "assignments": {
          "assignments": [
            {
              "id": "email_id",
              "name": "email_id",
              "value": "={{ $json.id }}",
              "type": "string"
            },
            {
              "id": "from",
              "name": "from",
              "value": "={{ $json.from }}",
              "type": "string"
            },
            {
              "id": "subject",
              "name": "subject",
              "value": "={{ $json.subject }}",
              "type": "string"
            },
            {
              "id": "body",
              "name": "body",
              "value": "={{ $json.text || $json.snippet || '' }}",
              "type": "string"
            },
            {
              "id": "date",
              "name": "date",
              "value": "={{ $json.date }}",
              "type": "string"
            }
          ]
        }
      }
    },
    {
      "id": "claude-llm",
      "name": "Claude Triage Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "typeVersion": 1.3,
      "position": [
        440,
        0
      ],
      "parameters": {
        "model": "claude-haiku-4-5-20251001",
        "options": {
          "maxTokens": 800
        }
      },
      "credentials": {
        "anthropicApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "triage-chain",
      "name": "Triage + Draft Reply",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.4,
      "position": [
        440,
        0
      ],
      "parameters": {
        "promptType": "define",
        "text": "=You are an email triage assistant. Analyze this email and return a JSON object with:\n- category: one of \"support\", \"billing\", \"sales_inquiry\", \"partnership\", \"spam\", \"personal\", \"urgent\", \"newsletter\", \"other\"\n- priority: \"high\" | \"medium\" | \"low\"\n- one_line_summary: max 15 words describing what the email is about\n- draft_reply: a short, professional reply draft (2-4 sentences). Write in first person as the business owner. Leave [PLACEHOLDER] where personalization is needed.\n- label: the Gmail label to apply, same as category\n\nEmail:\nFrom: {{ $('Extract Email Fields').item.json.from }}\nSubject: {{ $('Extract Email Fields').item.json.subject }}\nBody: {{ $('Extract Email Fields').item.json.body }}\n\nReturn only valid JSON, no markdown fencing."
      }
    },
    {
      "id": "parse-triage",
      "name": "Parse Triage Response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        660,
        0
      ],
      "parameters": {
        "jsCode": "const raw = $input.first().json.text || '{}';\nlet parsed = {};\ntry {\n  parsed = JSON.parse(raw.replace(/```json|```/g, '').trim());\n} catch(e) {\n  parsed = { category: 'other', priority: 'medium', one_line_summary: 'Parse error', draft_reply: '', label: 'other' };\n}\nconst prev = $('Extract Email Fields').first().json;\nreturn [{ json: { ...prev, ...parsed } }];"
      }
    },
    {
      "id": "gmail-draft",
      "name": "Create Gmail Draft",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        880,
        -120
      ],
      "parameters": {
        "operation": "createDraft",
        "sendTo": "={{ $json.from }}",
        "subject": "=Re: {{ $json.subject }}",
        "message": "={{ $json.draft_reply }}",
        "options": {}
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "check-urgent",
      "name": "Is Urgent?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        880,
        80
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": false
          },
          "conditions": [
            {
              "id": "priority-check",
              "leftValue": "={{ $json.priority }}",
              "rightValue": "high",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ]
        }
      }
    },
    {
      "id": "slack-urgent",
      "name": "Slack Urgent Alert",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.3,
      "position": [
        1100,
        0
      ],
      "parameters": {
        "operation": "post",
        "channel": "YOUR_SLACK_CHANNEL_ID",
        "text": "=*Urgent email needs your attention*\n*From:* {{ $json.from }}\n*Subject:* {{ $json.subject }}\n*Summary:* {{ $json.one_line_summary }}\n*Category:* {{ $json.category }}\n\nDraft reply is ready in Gmail."
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "Gmail Poll Unread": {
      "main": [
        [
          {
            "node": "Extract Email Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Email Fields": {
      "main": [
        [
          {
            "node": "Triage + Draft Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude Triage Model": {
      "ai_languageModel": [
        [
          {
            "node": "Triage + Draft Reply",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Triage + Draft Reply": {
      "main": [
        [
          {
            "node": "Parse Triage Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Triage Response": {
      "main": [
        [
          {
            "node": "Create Gmail Draft",
            "type": "main",
            "index": 0
          },
          {
            "node": "Is Urgent?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Urgent?": {
      "main": [
        [
          {
            "node": "Slack Urgent Alert",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    }
  }
}