{
  "id": "DemoApparelWf002",
  "name": "02 · Approval queue in Telegram (Demo Apparel Co.)",
  "nodes": [
    {
      "parameters": {
        "content": "## 1 · Send new drafts to the owner\nEvery 15 min, drafts marked \"needs review\" go to Telegram with Approve / Edit / Reject buttons.",
        "height": 330,
        "width": 920,
        "color": 5
      },
      "id": "304e74e3-78a6-44d0-9233-47d56aa8524f",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -50,
        -140
      ]
    },
    {
      "parameters": {
        "content": "## 2 · Owner decides\nButton presses and replies come back through the Telegram Trigger and are routed by action.",
        "height": 670,
        "width": 1000,
        "color": 6
      },
      "id": "fcf2dd9a-d63e-4650-88ae-deceba07cab4",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -50,
        230
      ]
    },
    {
      "parameters": {
        "content": "## 3 · Edits become rules\nClaude turns each edit into a reusable rule (\"never say X\", \"use Y instead\"), saves it to the Brand kit and re-drafts. Next round needs fewer edits.",
        "height": 330,
        "width": 920,
        "color": 4
      },
      "id": "a903b4b0-01fe-4e6b-b6f7-3e05351344cc",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        750,
        910
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 15
            }
          ]
        }
      },
      "id": "05359e4a-32db-4690-85df-03ee550ee2d6",
      "name": "Every 15 minutes",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.4,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/REPLACE_WITH_CONTENT_CALENDAR_SHEET_ID/edit"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Content calendar"
        },
        "options": {},
        "filtersUI": {
          "values": [
            {
              "lookupColumn": "status",
              "lookupValue": "needs review"
            }
          ]
        }
      },
      "id": "a0975a71-f9ba-49cd-9543-62132860dff3",
      "name": "Read drafts needing review",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        240,
        0
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "credGSheets00001",
          "name": "Google Sheets (client)"
        }
      }
    },
    {
      "parameters": {
        "chatId": "REPLACE_WITH_OWNER_CHAT_ID",
        "text": "={{ $json.channel }} · {{ $json.segment }} · {{ $json.planned_date || 'no date yet' }}\n\n{{ $json.text }}\n\nRule check: {{ $json.rule_check }} {{ $json.rule_flags }}\nID {{ $json.draft_id }}",
        "additionalFields": {
          "appendAttribution": false
        },
        "replyMarkup": "inlineKeyboard",
        "inlineKeyboard": {
          "rows": [
            {
              "row": {
                "buttons": [
                  {
                    "text": "Approve",
                    "additionalFields": {
                      "callback_data": "=approve|{{ $json.draft_id }}"
                    }
                  },
                  {
                    "text": "Edit",
                    "additionalFields": {
                      "callback_data": "=edit|{{ $json.draft_id }}"
                    }
                  }
                ]
              }
            },
            {
              "row": {
                "buttons": [
                  {
                    "text": "Reject: off-brand",
                    "additionalFields": {
                      "callback_data": "=reject|{{ $json.draft_id }}|off-brand"
                    }
                  },
                  {
                    "text": "Reject: wrong timing",
                    "additionalFields": {
                      "callback_data": "=reject|{{ $json.draft_id }}|timing"
                    }
                  }
                ]
              }
            }
          ]
        }
      },
      "id": "45737a0f-9625-4fc1-986c-8ebfee355e20",
      "name": "Send draft with buttons",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        480,
        0
      ],
      "credentials": {
        "telegramApi": {
          "id": "credTelegram0001",
          "name": "Telegram bot (client)"
        }
      }
    },
    {
      "parameters": {
        "operation": "update",
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/REPLACE_WITH_CONTENT_CALENDAR_SHEET_ID/edit"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Content calendar"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "draft_id": "={{ $('Read drafts needing review').item.json.draft_id }}",
            "status": "in review",
            "telegram_message_id": "={{ $json.result.message_id }}"
          },
          "matchingColumns": [
            "draft_id"
          ],
          "schema": [
            {
              "id": "draft_id",
              "displayName": "draft_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "status",
              "displayName": "status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "telegram_message_id",
              "displayName": "telegram_message_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            }
          ]
        },
        "options": {}
      },
      "id": "ec543840-69d2-462a-9d20-0c529d42fc29",
      "name": "Mark as in review",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        720,
        0
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "credGSheets00001",
          "name": "Google Sheets (client)"
        }
      }
    },
    {
      "parameters": {
        "updates": [
          "callback_query",
          "message"
        ],
        "additionalFields": {}
      },
      "id": "820264de-fa69-4c2c-af2d-160893b9245d",
      "name": "Owner taps a button or replies",
      "type": "n8n-nodes-base.telegramTrigger",
      "typeVersion": 1.5,
      "position": [
        0,
        560
      ],
      "credentials": {
        "telegramApi": {
          "id": "credTelegram0001",
          "name": "Telegram bot (client)"
        }
      },
      "webhookId": "6f1d2c1e-0b7a-4a52-9a43-0d3e5a000002"
    },
    {
      "parameters": {
        "jsCode": "// Button press -> callback_data \"action|draft_id|reason\".\n// Text reply   -> a reply to our \"EDIT <draft_id>\" prompt.\nconst u = $json;\nif (u.callback_query) {\n  const [action, draft_id, reason = ''] = String(u.callback_query.data || '').split('|');\n  return { json: {\n    action, draft_id, reason,\n    chat_id: u.callback_query.message.chat.id,\n    original: u.callback_query.message.text || '',\n  } };\n}\n\nconst msg = u.message || {};\nconst prompt = msg.reply_to_message?.text || '';\nconst match = prompt.match(/^EDIT (\\S+)/);\nif (!match) return { json: { action: 'ignore' } };\n\nconst original = prompt.split('Current draft:')[1]?.trim() || '';\nreturn { json: {\n  action: 'edit_text',\n  draft_id: match[1],\n  chat_id: msg.chat.id,\n  edit_text: msg.text || '',\n  system_prompt: 'You maintain the brand voice rules for Demo Apparel Co. Return ONLY valid JSON.',\n  user_prompt: [\n    'Original draft:', original, '',\n    'Owner feedback / edited version:', msg.text || '', '',\n    '1. Turn the feedback into ONE short reusable rule for future drafts, e.g. \"Never say X\" or \"Use Y instead of Z\".',\n    '2. Rewrite the draft applying the feedback.',\n    'JSON shape: {\"rule_type\":\"voice_rule|banned_phrase\",\"rule\":\"...\",\"redraft\":\"...\"}',\n  ].join('\\n'),\n} };",
        "mode": "runOnceForEachItem"
      },
      "id": "3cc3e4f3-d51c-427b-bf06-b806e02f29f1",
      "name": "Parse action",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        240,
        560
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 3
                },
                "conditions": [
                  {
                    "id": "8baa5d34-f52d-48a0-9868-4eee64dcb153",
                    "leftValue": "={{ $json.action }}",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "rightValue": "approve"
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Approve"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 3
                },
                "conditions": [
                  {
                    "id": "049fa891-c33c-4edc-9236-4bb181259e2a",
                    "leftValue": "={{ $json.action }}",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "rightValue": "edit"
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Edit"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 3
                },
                "conditions": [
                  {
                    "id": "8719a5fb-fc08-4e40-acb2-c796e01d0787",
                    "leftValue": "={{ $json.action }}",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "rightValue": "reject"
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Reject"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 3
                },
                "conditions": [
                  {
                    "id": "f6a7d41d-4bca-4347-8752-a9e4a17e8c75",
                    "leftValue": "={{ $json.action }}",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "rightValue": "edit_text"
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Edit text received"
            }
          ]
        },
        "options": {}
      },
      "id": "5f7cddcc-7bb6-4d40-ac26-335f300e1608",
      "name": "Route by action",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.4,
      "position": [
        480,
        560
      ]
    },
    {
      "parameters": {
        "operation": "update",
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/REPLACE_WITH_CONTENT_CALENDAR_SHEET_ID/edit"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Content calendar"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "draft_id": "={{ $json.draft_id }}",
            "status": "approved",
            "reviewed_at": "={{ $now.toISO() }}"
          },
          "matchingColumns": [
            "draft_id"
          ],
          "schema": [
            {
              "id": "draft_id",
              "displayName": "draft_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "status",
              "displayName": "status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "reviewed_at",
              "displayName": "reviewed_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            }
          ]
        },
        "options": {}
      },
      "id": "3fa4b834-61c0-43ba-99cf-3f6300fff518",
      "name": "Set status: approved",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        800,
        380
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "credGSheets00001",
          "name": "Google Sheets (client)"
        }
      }
    },
    {
      "parameters": {
        "chatId": "={{ $json.chat_id }}",
        "text": "=EDIT {{ $json.draft_id }}\nReply to this message with your corrected version, or a note on what to change.\n\nCurrent draft:\n{{ $json.original }}",
        "additionalFields": {
          "appendAttribution": false
        },
        "replyMarkup": "forceReply",
        "forceReply": {
          "force_reply": true
        }
      },
      "id": "979bcbfd-dca0-400e-94d4-6b24aff3cc9a",
      "name": "Ask owner for the edit",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        800,
        560
      ],
      "credentials": {
        "telegramApi": {
          "id": "credTelegram0001",
          "name": "Telegram bot (client)"
        }
      }
    },
    {
      "parameters": {
        "operation": "update",
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/REPLACE_WITH_CONTENT_CALENDAR_SHEET_ID/edit"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Content calendar"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "draft_id": "={{ $json.draft_id }}",
            "status": "rejected",
            "reject_reason": "={{ $json.reason }}",
            "reviewed_at": "={{ $now.toISO() }}"
          },
          "matchingColumns": [
            "draft_id"
          ],
          "schema": [
            {
              "id": "draft_id",
              "displayName": "draft_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "status",
              "displayName": "status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "reject_reason",
              "displayName": "reject_reason",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "reviewed_at",
              "displayName": "reviewed_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            }
          ]
        },
        "options": {}
      },
      "id": "8fe61981-5bd6-41fa-8440-afa79ad95c7c",
      "name": "Set status: rejected",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        800,
        740
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "credGSheets00001",
          "name": "Google Sheets (client)"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.anthropic.com/v1/messages",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({\n  model: 'claude-sonnet-5-5',\n  max_tokens: 8000,\n  system: $json.system_prompt,\n  messages: [{ role: 'user', content: $json.user_prompt }]\n}) }}",
        "options": {
          "timeout": 180000
        }
      },
      "id": "8bd36e82-5bb1-42df-91f9-30bad98519f1",
      "name": "Turn edit into a rule with Claude",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.5,
      "position": [
        800,
        1050
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "credAnthropic001",
          "name": "Anthropic API"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Claude replies with content blocks; take the text block and parse it as JSON.\nfunction claudeJson(resp) {\n  const block = (resp.content || []).find((b) => b.type === 'text');\n  if (!block) throw new Error('Claude returned no text (stop_reason: ' + resp.stop_reason + ')');\n  const raw = block.text.trim().replace(/^```(?:json)?\\s*/i, '').replace(/```$/, '').trim();\n  return JSON.parse(raw);\n}\n\nconst ctx = $('Parse action').item.json;\nconst r = claudeJson($json);\nreturn { json: {\n  draft_id: ctx.draft_id,\n  type: r.rule_type === 'banned_phrase' ? 'banned_phrase' : 'voice_rule',\n  value: r.rule,\n  channel: '',\n  notes: `learned from edit on ${ctx.draft_id}, ${$now.toISODate()}`,\n  redraft: r.redraft,\n} };",
        "mode": "runOnceForEachItem"
      },
      "id": "940199e1-61d0-4403-a562-34957c88501c",
      "name": "Parse rule and redraft",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1040,
        1050
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/REPLACE_WITH_BRAND_KIT_SHEET_ID/edit"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Brand kit"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "type": "={{ $json.type }}",
            "value": "={{ $json.value }}",
            "channel": "",
            "notes": "={{ $json.notes }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "type",
              "displayName": "type",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "value",
              "displayName": "value",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "channel",
              "displayName": "channel",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "notes",
              "displayName": "notes",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ]
        },
        "options": {}
      },
      "id": "d952fb13-eb6b-4c15-a2de-25d92e3ad5a4",
      "name": "Append rule to Brand kit",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        1280,
        1050
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "credGSheets00001",
          "name": "Google Sheets (client)"
        }
      }
    },
    {
      "parameters": {
        "operation": "update",
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/REPLACE_WITH_CONTENT_CALENDAR_SHEET_ID/edit"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Content calendar"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "draft_id": "={{ $('Parse rule and redraft').item.json.draft_id }}",
            "text": "={{ $('Parse rule and redraft').item.json.redraft }}",
            "status": "needs review",
            "rule_flags": "=redrafted after edit: {{ $('Parse rule and redraft').item.json.value }}"
          },
          "matchingColumns": [
            "draft_id"
          ],
          "schema": [
            {
              "id": "draft_id",
              "displayName": "draft_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "text",
              "displayName": "text",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "status",
              "displayName": "status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "rule_flags",
              "displayName": "rule_flags",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            }
          ]
        },
        "options": {}
      },
      "id": "742523cb-c95e-4aff-93fa-a0bee3a1deac",
      "name": "Save redraft for review",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        1520,
        1050
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "credGSheets00001",
          "name": "Google Sheets (client)"
        }
      }
    }
  ],
  "connections": {
    "Every 15 minutes": {
      "main": [
        [
          {
            "node": "Read drafts needing review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read drafts needing review": {
      "main": [
        [
          {
            "node": "Send draft with buttons",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send draft with buttons": {
      "main": [
        [
          {
            "node": "Mark as in review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Owner taps a button or replies": {
      "main": [
        [
          {
            "node": "Parse action",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse action": {
      "main": [
        [
          {
            "node": "Route by action",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by action": {
      "main": [
        [
          {
            "node": "Set status: approved",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Ask owner for the edit",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Set status: rejected",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Turn edit into a rule with Claude",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Turn edit into a rule with Claude": {
      "main": [
        [
          {
            "node": "Parse rule and redraft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse rule and redraft": {
      "main": [
        [
          {
            "node": "Append rule to Brand kit",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append rule to Brand kit": {
      "main": [
        [
          {
            "node": "Save redraft for review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": []
}
