{
  "name": "Inbox Auto-Draft",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 15
            }
          ]
        }
      },
      "id": "b2b70001-0000-4000-8000-000000000001",
      "name": "Every 15 minutes",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [-140, 0]
    },
    {
      "parameters": {
        "resource": "message",
        "operation": "getAll",
        "returnAll": false,
        "limit": 10,
        "simple": true,
        "filters": {
          "q": "is:unread newer_than:1d -category:promotions -category:social -category:updates"
        },
        "options": {}
      },
      "id": "b2b70001-0000-4000-8000-000000000002",
      "name": "Fetch new email",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [80, 0],
      "credentials": {
        "gmailOAuth2": {
          "id": "",
          "name": "YOUR_GMAIL_CREDENTIAL"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Two jobs: skip mail already drafted, and pin down the field names.\n// Gmail's output shape moves around between accounts and n8n versions —\n// sometimes from/subject sit at the top level, sometimes inside headers.\n// Normalise here once so everything downstream can just read .from .subject .text\n//\n// staticData only persists between runs while the workflow is ACTIVE.\n// Running it manually in the editor will re-process the same mail. That is expected.\nconst store = $getWorkflowStaticData('global');\nstore.processed = store.processed || [];\nconst first = (...v) => v.find(x => typeof x === 'string' && x.trim() !== '') || '';\nconst out = [];\nfor (const item of $input.all()) {\n  const j = item.json;\n  const raw = j.headers || (j.payload && j.payload.headers) || {};\n  const h = {};\n  if (Array.isArray(raw)) { for (const x of raw) h[String(x.name || '').toLowerCase()] = x.value; }\n  else { for (const k of Object.keys(raw)) h[k.toLowerCase()] = raw[k]; }\n  const id = j.id || j.messageId;\n  if (id && store.processed.includes(id)) continue;\n  if (id) store.processed.push(id);\n  out.push({ json: {\n    id,\n    from: first(j.from, j.From, h.from),\n    subject: first(j.subject, j.Subject, h.subject),\n    text: first(j.text, j.textPlain, j.body, j.snippet),\n    _debugKeys: Object.keys(j).join(', ')\n  } });\n}\nstore.processed = store.processed.slice(-500);\nreturn out;"
      },
      "id": "b2b70001-0000-4000-8000-000000000003",
      "name": "Skip already drafted",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [300, 0]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ systemInstruction: { parts: [ { text: \"You draft replies on behalf of a solo creator. Write 3-4 sentences, first person, plain words, no corporate filler, no exclamation marks. If the message is a pitch or cold outreach, write a short polite decline instead. If it asks for a price or a date you cannot know, leave [FILL] where that detail goes. Reply with JSON only, shaped {\\\"reply\\\": string, \\\"kind\\\": string} where kind is one of: reply, decline, ignore.\" } ] }, contents: [ { role: \"user\", parts: [ { text: `From: ${$json.from}\\nSubject: ${$json.subject}\\n\\n${String($json.text || $json.snippet || '').slice(0, 4000)}` } ] } ], generationConfig: { responseMimeType: \"application/json\" } }) }}",
        "options": {}
      },
      "id": "b2b70001-0000-4000-8000-000000000004",
      "name": "Draft the reply",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [520, 0],
      "credentials": {
        "httpHeaderAuth": {
          "id": "",
          "name": "YOUR_GEMINI_HEADER_AUTH"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "YOUR_DISCORD_WEBHOOK_URL",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ content: `**DRAFT — ${JSON.parse($json.candidates[0].content.parts[0].text).kind}**\\nFrom: ${$('Skip already drafted').item.json.from}\\nRe: ${$('Skip already drafted').item.json.subject}\\n\\n${JSON.parse($json.candidates[0].content.parts[0].text).reply}`.slice(0, 1900) }) }}",
        "options": {}
      },
      "id": "b2b70001-0000-4000-8000-000000000005",
      "name": "Send draft to Discord",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [740, 0]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "mode": "list",
          "value": "YOUR_SPREADSHEET_ID"
        },
        "sheetName": {
          "mode": "list",
          "value": "DraftLog"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "messageId": "={{ $('Skip already drafted').item.json.id }}",
            "from": "={{ $('Skip already drafted').item.json.from }}",
            "subject": "={{ $('Skip already drafted').item.json.subject }}",
            "kind": "={{ JSON.parse($json.candidates[0].content.parts[0].text).kind }}",
            "draft": "={{ JSON.parse($json.candidates[0].content.parts[0].text).reply }}",
            "sentAt": "={{ $now.toISO() }}"
          }
        }
      },
      "id": "b2b70001-0000-4000-8000-000000000006",
      "name": "Log it",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [960, 0],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "",
          "name": "YOUR_GOOGLE_SHEETS_CREDENTIAL"
        }
      }
    }
  ],
  "connections": {
    "Every 15 minutes": {
      "main": [[{ "node": "Fetch new email", "type": "main", "index": 0 }]]
    },
    "Fetch new email": {
      "main": [[{ "node": "Skip already drafted", "type": "main", "index": 0 }]]
    },
    "Skip already drafted": {
      "main": [[{ "node": "Draft the reply", "type": "main", "index": 0 }]]
    },
    "Draft the reply": {
      "main": [[{ "node": "Send draft to Discord", "type": "main", "index": 0 }]]
    },
    "Send draft to Discord": {
      "main": [[{ "node": "Log it", "type": "main", "index": 0 }]]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "id": "wf-inbox-autodraft"
}
