{
  "id": "DemoApparelWf003",
  "name": "03 · Shopify product launch pack (Demo Apparel Co.)",
  "nodes": [
    {
      "parameters": {
        "content": "## 1 · New product\nFires on products/create. Only continues for active, published products that have a photo.",
        "height": 330,
        "width": 440,
        "color": 5
      },
      "id": "82935d18-ec3d-4323-afde-cc5eb203977b",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -50,
        160
      ]
    },
    {
      "parameters": {
        "content": "## 2 · Launch pack with Claude\nIG caption + alt text, 30 s Reels script with shots, email section, 3 hashtag sets, in the brand voice.",
        "height": 330,
        "width": 920,
        "color": 6
      },
      "id": "5e7181eb-9625-48d8-9475-5703450c1aab",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        430,
        160
      ]
    },
    {
      "parameters": {
        "content": "## 3 · Schedule for review\nAssets spread over launch week as \"needs review\" rows; owner gets a heads-up.",
        "height": 330,
        "width": 680,
        "color": 4
      },
      "id": "cd64fa17-d9fe-477e-9521-b0d9e8b1e7f9",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1390,
        160
      ]
    },
    {
      "parameters": {
        "authentication": "accessToken",
        "topic": "products/create"
      },
      "id": "fae5403f-aedf-44d8-b537-95ff460098bd",
      "name": "New product in Shopify",
      "type": "n8n-nodes-base.shopifyTrigger",
      "typeVersion": 1,
      "position": [
        0,
        300
      ],
      "credentials": {
        "shopifyAccessTokenApi": {
          "id": "credShopify00001",
          "name": "Shopify (client)"
        }
      },
      "webhookId": "3a8c5f0e-1d2b-4c6e-8f90-0d3e5a000003"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 3
          },
          "conditions": [
            {
              "id": "abbc17dc-03ce-4370-b241-cad9dd7a9a91",
              "leftValue": "={{ $json.status }}",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "rightValue": "active"
            },
            {
              "id": "674fb11e-1ae5-4d25-99a8-947e2d4d83be",
              "leftValue": "={{ $json.published_at }}",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              }
            },
            {
              "id": "bf6b63cd-8061-4f22-b8e4-187db4368187",
              "leftValue": "={{ ($json.images || []).length }}",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "rightValue": 0
            }
          ],
          "combinator": "and"
        },
        "looseTypeValidation": true,
        "options": {}
      },
      "id": "6033eff3-4da3-45cd-94cf-ff69bdeb4a79",
      "name": "Published with an image?",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2.3,
      "position": [
        240,
        300
      ]
    },
    {
      "parameters": {
        "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"
        },
        "options": {}
      },
      "id": "7cdd07cb-de6f-4d20-8bbe-3ac5d43f3a47",
      "name": "Load brand kit",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        480,
        300
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "credGSheets00001",
          "name": "Google Sheets (client)"
        }
      },
      "executeOnce": true,
      "alwaysOutputData": true
    },
    {
      "parameters": {
        "jsCode": "// \"Brand kit\" sheet: one row per rule. Columns: type | value | channel | notes\n// type = voice_rule | banned_phrase | segment | required_cta | max_length\nconst kit = { brand: 'Demo Apparel Co.', voice: [], banned: [], segments: [], ctas: [], maxLength: {} };\nfor (const { json: r } of $input.all()) {\n  const type = String(r.type || '').trim().toLowerCase();\n  const value = String(r.value || '').trim();\n  if (!value) continue;\n  if (type === 'voice_rule') kit.voice.push(value);\n  else if (type === 'banned_phrase') kit.banned.push(value);\n  else if (type === 'segment') kit.segments.push(value);\n  else if (type === 'required_cta') kit.ctas.push(value);\n  else if (type === 'max_length' && r.channel) kit.maxLength[String(r.channel).trim().toLowerCase()] = Number(value);\n}\n// Safe defaults so a half-filled sheet still works\nif (!kit.segments.length) kit.segments = ['active customers', 'gift buyers', 'coaches & teams', 'community'];\nkit.maxLength = { instagram: 2200, tiktok_script: 900, email_block: 1200, blog_intro: 700, ...kit.maxLength };\n\nconst p = $('Published with an image?').first().json;\nconst plain = (html) => String(html || '').replace(/<[^>]+>/g, ' ').replace(/\\s+/g, ' ').trim();\nconst product = {\n  title: p.title,\n  handle: p.handle,\n  type: p.product_type,\n  tags: p.tags,\n  description: plain(p.body_html).slice(0, 1500),\n  price: p.variants?.[0]?.price,\n  image_url: p.images?.[0]?.src,\n  url: 'https://REPLACE_WITH_STORE_DOMAIN/products/' + p.handle,\n};\n\nconst system = [\n  `You are the launch copywriter for ${kit.brand}, a small apparel and lifestyle brand.`,\n  'Voice rules:', ...kit.voice.map((v) => '- ' + v),\n  'Never use: ' + (kit.banned.join('; ') || 'none listed'),\n  'Return ONLY valid JSON. No prose, no markdown fences.',\n].join('\\n');\n\nconst user = [\n  'New product: ' + JSON.stringify(product),\n  'Audience segments: ' + kit.segments.join(', '),\n  'Create a launch pack:',\n  '- instagram: caption (max 2200 chars, ends with a CTA) + alt_text for the product photo (max 125 chars)',\n  '- reels: 30-second script as shots [{seconds, shot, on_screen_text, voiceover}], total <= 30 s',\n  '- email_section: headline, body (max 600 chars), cta',\n  '- hashtag_sets: exactly 3 sets of 5-12 hashtags each',\n  'JSON shape: {\"instagram\":{\"caption\":\"\",\"alt_text\":\"\"},\"reels\":{\"shots\":[]},\"email_section\":{\"headline\":\"\",\"body\":\"\",\"cta\":\"\"},\"hashtag_sets\":[[],[],[]]}',\n].join('\\n');\n\nreturn [{ json: { kit, product, system_prompt: system, user_prompt: user } }];"
      },
      "id": "ca4b3f4d-5d29-4ab5-a9da-4c40d5c260f5",
      "name": "Brand rules + product prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        720,
        300
      ]
    },
    {
      "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": "12f4c327-1cf5-46d2-80d4-7d79b9e1b65f",
      "name": "Create launch pack with Claude",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.5,
      "position": [
        960,
        300
      ],
      "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 { kit, product } = $('Brand rules + product prompt').first().json;\nconst pack = claudeJson($input.first().json);\nconst lower = (s) => String(s || '').toLowerCase();\nconst flags = [];\n\nconst allText = [pack.instagram?.caption, pack.email_section?.body, ...(pack.reels?.shots || []).map((s) => s.voiceover)].join(' ');\nfor (const phrase of kit.banned) if (lower(allText).includes(lower(phrase))) flags.push(`banned phrase \"${phrase}\"`);\nif ((pack.instagram?.caption || '').length > 2200) flags.push('IG caption over 2200 chars');\nif ((pack.instagram?.alt_text || '').length > 125) flags.push('alt text over 125 chars');\nconst seconds = (pack.reels?.shots || []).reduce((t, s) => {\n  const m = String(s.seconds || '').match(/(\\d+)\\s*-\\s*(\\d+)/);\n  return m ? Math.max(t, Number(m[2])) : t;\n}, 0);\nif (seconds > 30) flags.push(`Reels script runs ${seconds}s (max 30)`);\n(pack.hashtag_sets || []).forEach((set, i) => {\n  if (set.length < 5 || set.length > 12) flags.push(`hashtag set ${i + 1} has ${set.length} tags`);\n});\nif ((pack.hashtag_sets || []).length !== 3) flags.push('expected 3 hashtag sets');\n\nreturn [{ json: { product, pack, rule_check: flags.length ? 'FLAGGED' : 'OK', rule_flags: flags.join(' | ') } }];"
      },
      "id": "753ae05f-ea6d-47e0-bf11-1786c20b0b13",
      "name": "Check pack against rules",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1200,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "// Launch day = tomorrow. Day offsets per asset; owner can move dates in the sheet.\nconst { product, pack, rule_check, rule_flags } = $input.first().json;\nconst day = (n) => $today.plus({ days: 1 + n }).toISODate();\nconst tags = (i) => (pack.hashtag_sets?.[i] || []).join(' ');\nconst reels = (pack.reels?.shots || [])\n  .map((s, i) => `${i + 1}. [${s.seconds}] ${s.shot} | text: ${s.on_screen_text} | VO: ${s.voiceover}`)\n  .join('\\n');\nconst stamp = Date.now().toString(36);\n\nconst rows = [\n  { planned_date: day(0), channel: 'instagram', text: pack.instagram.caption + '\\n\\n' + tags(0), notes: 'alt text: ' + pack.instagram.alt_text },\n  { planned_date: day(1), channel: 'tiktok_script', text: reels + '\\n\\n' + tags(1), notes: '30s Reels / TikTok' },\n  { planned_date: day(2), channel: 'email_block', text: `${pack.email_section.headline}\\n\\n${pack.email_section.body}\\n\\n${pack.email_section.cta}`, notes: 'for weekly campaign (workflow 5)' },\n  { planned_date: day(4), channel: 'instagram', text: pack.instagram.caption + '\\n\\n' + tags(2), notes: 'second post, hashtag set 3' },\n];\n\nreturn rows.map((r, i) => ({ json: {\n  draft_id: `l-${stamp}-${i}`,\n  created_at: $now.toISO(),\n  source_id: 'launch-' + product.handle,\n  source: 'Product launch: ' + product.title,\n  segment: 'all',\n  image_url: product.image_url,\n  rule_check, rule_flags,\n  status: 'needs review',\n  ...r,\n} }));"
      },
      "id": "35fcc38a-b9f6-4beb-b4b9-21c5d2bd37d0",
      "name": "Spread over launch week",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1440,
        300
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "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": "autoMapInputData",
          "value": {},
          "matchingColumns": [],
          "schema": []
        },
        "options": {}
      },
      "id": "b002e00e-413e-433d-9fb6-a43fcf17b2c9",
      "name": "Add to Content calendar",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        1680,
        300
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "credGSheets00001",
          "name": "Google Sheets (client)"
        }
      }
    },
    {
      "parameters": {
        "chatId": "REPLACE_WITH_OWNER_CHAT_ID",
        "text": "=Launch pack ready: {{ $('Check pack against rules').first().json.product.title }}\n{{ $('Spread over launch week').all().length }} drafts added to the Content calendar for launch week.\nRule check: {{ $('Check pack against rules').first().json.rule_check }} {{ $('Check pack against rules').first().json.rule_flags }}\nThey will arrive in the approval queue within 15 minutes.",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "id": "054362c1-3a54-4e1d-849b-78984e08edd1",
      "name": "Tell owner on Telegram",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        1920,
        300
      ],
      "credentials": {
        "telegramApi": {
          "id": "credTelegram0001",
          "name": "Telegram bot (client)"
        }
      },
      "executeOnce": true
    }
  ],
  "connections": {
    "New product in Shopify": {
      "main": [
        [
          {
            "node": "Published with an image?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Published with an image?": {
      "main": [
        [
          {
            "node": "Load brand kit",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load brand kit": {
      "main": [
        [
          {
            "node": "Brand rules + product prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Brand rules + product prompt": {
      "main": [
        [
          {
            "node": "Create launch pack with Claude",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create launch pack with Claude": {
      "main": [
        [
          {
            "node": "Check pack against rules",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check pack against rules": {
      "main": [
        [
          {
            "node": "Spread over launch week",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Spread over launch week": {
      "main": [
        [
          {
            "node": "Add to Content calendar",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add to Content calendar": {
      "main": [
        [
          {
            "node": "Tell owner on Telegram",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": []
}
