{
  "id": "DemoApparelWf005",
  "name": "05 · Weekly email campaign drafts (Demo Apparel Co.)",
  "nodes": [
    {
      "parameters": {
        "content": "## 1 · This week's approved email blocks\nPulled from the Content calendar and grouped into 3 segments: new, repeat, lapsed 90 days.",
        "height": 330,
        "width": 680,
        "color": 5
      },
      "id": "bcb54264-1de8-4a5e-8563-7593243ee65c",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -50,
        60
      ]
    },
    {
      "parameters": {
        "content": "## 2 · Claude assembles each campaign\nKeeps approved wording, adds subject A/B options, preview text and HTML.",
        "height": 330,
        "width": 440,
        "color": 6
      },
      "id": "f1268115-2559-4a33-a8a2-2815d330cf7d",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        670,
        60
      ]
    },
    {
      "parameters": {
        "content": "## 3 · Klaviyo, as DRAFT only\nTemplate + campaign + template assignment via the Klaviyo API. No send job is ever created.",
        "height": 330,
        "width": 680,
        "color": 3
      },
      "id": "aac53357-a2a6-49a4-aa5b-15bcf443e47e",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -50,
        440
      ]
    },
    {
      "parameters": {
        "content": "## 4 · Close the loop\nCalendar rows link to the drafts; owner gets one Telegram message.",
        "height": 330,
        "width": 680,
        "color": 4
      },
      "id": "d972b9da-4dc9-4806-96bc-441469814e11",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        670,
        440
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                1
              ],
              "triggerAtHour": 7,
              "triggerAtMinute": 30
            }
          ]
        }
      },
      "id": "c2df73f8-3ac4-4c9f-b756-b1966c0d99dd",
      "name": "Every Monday 07:30",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.4,
      "position": [
        0,
        200
      ]
    },
    {
      "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": "channel",
              "lookupValue": "email_block"
            }
          ]
        }
      },
      "id": "da36a0da-facf-4e1e-8e81-62b6c9e69a4d",
      "name": "Read email blocks",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        240,
        200
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "credGSheets00001",
          "name": "Google Sheets (client)"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Approved email blocks planned for this week (Mon-Sun), one campaign per segment.\n// Audience ids are Klaviyo list/segment ids - not secrets, but client-specific.\nconst SEGMENTS = [\n  { key: 'new', name: 'New customers', audience_id: 'REPLACE_WITH_KLAVIYO_SEGMENT_NEW', brief: 'first order in the last 30 days: welcome, how to care for the product, community' },\n  { key: 'repeat', name: 'Repeat customers', audience_id: 'REPLACE_WITH_KLAVIYO_SEGMENT_REPEAT', brief: '2+ orders: new arrivals first, early access tone' },\n  { key: 'lapsed', name: 'Lapsed 90 days', audience_id: 'REPLACE_WITH_KLAVIYO_SEGMENT_LAPSED', brief: 'no order in 90+ days: what is new since their last visit, soft reason to come back' },\n];\nconst weekStart = $today.startOf('week');\nconst weekEnd = weekStart.plus({ days: 6 });\nconst blocks = $input.all().map((i) => i.json).filter((r) => {\n  if (r.status !== 'approved' || !r.planned_date) return false;\n  const d = DateTime.fromISO(String(r.planned_date));\n  return d >= weekStart && d <= weekEnd;\n});\nif (!blocks.length) return [];\n\nconst week = 'Week ' + weekStart.toFormat('W yyyy');\nreturn SEGMENTS.map((seg) => ({ json: {\n  ...seg,\n  week,\n  draft_ids: blocks.map((b) => b.draft_id),\n  campaign_name: `${week} · ${seg.name} (draft)`,\n  system_prompt: 'You assemble weekly emails for Demo Apparel Co., a small apparel and lifestyle brand. Keep the owner-approved wording of each block; only order, trim and connect them. Return ONLY valid JSON.',\n  user_prompt: [\n    `Segment: ${seg.name} - ${seg.brief}`,\n    'Approved content blocks (keep wording):',\n    ...blocks.map((b, i) => `[${i + 1}] (${b.source}) ${b.text}`),\n    '',\n    'Return: two subject line options for an A/B test, a preview text, and simple responsive HTML (inline styles, max 600px).',\n    'Add utm_source=klaviyo&utm_medium=email&utm_campaign=<source_id> to every link.',\n    'JSON shape: {\"subject_a\":\"\",\"subject_b\":\"\",\"preview_text\":\"\",\"html\":\"\"}',\n  ].join('\\n'),\n} }));"
      },
      "id": "b8f238d6-a076-4c68-8f17-33463311c649",
      "name": "Group this week's blocks by segment",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        480,
        200
      ]
    },
    {
      "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": "b3c75dab-12ec-4961-926b-cf48d2df3e24",
      "name": "Assemble campaign with Claude",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.5,
      "position": [
        720,
        200
      ],
      "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 seg = $(\"Group this week's blocks by segment\").item.json;\nconst c = claudeJson($json);\nreturn { json: {\n  segment: seg.name, audience_id: seg.audience_id, campaign_name: seg.campaign_name, draft_ids: seg.draft_ids,\n  subject_a: c.subject_a, subject_b: c.subject_b, preview_text: c.preview_text, html: c.html,\n} };",
        "mode": "runOnceForEachItem"
      },
      "id": "c9bd9ee7-6c90-4bfc-8bf5-e87b20a78cb2",
      "name": "Parse campaign",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        960,
        200
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://a.klaviyo.com/api/templates/",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "revision",
              "value": "2024-10-15"
            },
            {
              "name": "accept",
              "value": "application/vnd.api+json"
            },
            {
              "name": "content-type",
              "value": "application/vnd.api+json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ data: {\n  type: 'template',\n  attributes: { name: $json.campaign_name, editor_type: 'CODE', html: $json.html }\n} }) }}",
        "options": {}
      },
      "id": "54c6f4bd-dc5e-480e-8667-536df5da03a8",
      "name": "Klaviyo: create template",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.5,
      "position": [
        0,
        580
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "credKlaviyo00001",
          "name": "Klaviyo private API key (client)"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://a.klaviyo.com/api/campaigns/",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "revision",
              "value": "2024-10-15"
            },
            {
              "name": "accept",
              "value": "application/vnd.api+json"
            },
            {
              "name": "content-type",
              "value": "application/vnd.api+json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ data: {\n  type: 'campaign',\n  attributes: {\n    name: $('Parse campaign').item.json.campaign_name,\n    audiences: { included: [$('Parse campaign').item.json.audience_id], excluded: [] },\n    'campaign-messages': { data: [{\n      type: 'campaign-message',\n      attributes: {\n        channel: 'email',\n        label: $('Parse campaign').item.json.segment,\n        content: {\n          subject: $('Parse campaign').item.json.subject_a,\n          preview_text: $('Parse campaign').item.json.preview_text,\n          from_label: 'Demo Apparel Co.'\n        }\n      }\n    }] }\n  }\n} }) }}",
        "options": {}
      },
      "id": "ca33fc25-260b-4f99-8963-16cfb1a095ed",
      "name": "Klaviyo: create draft campaign",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.5,
      "position": [
        240,
        580
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "credKlaviyo00001",
          "name": "Klaviyo private API key (client)"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://a.klaviyo.com/api/campaign-message-assign-template/",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "revision",
              "value": "2024-10-15"
            },
            {
              "name": "accept",
              "value": "application/vnd.api+json"
            },
            {
              "name": "content-type",
              "value": "application/vnd.api+json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ data: {\n  type: 'campaign-message',\n  id: $json.data.relationships['campaign-messages'].data[0].id,\n  relationships: { template: { data: { type: 'template', id: $('Klaviyo: create template').item.json.data.id } } }\n} }) }}",
        "options": {}
      },
      "id": "6bc50443-2ee8-4dfa-a503-3fcfd0267ae8",
      "name": "Klaviyo: attach template",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.5,
      "position": [
        480,
        580
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "credKlaviyo00001",
          "name": "Klaviyo private API key (client)"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// One update per calendar row: which Klaviyo draft(s) used this block.\nconst campaigns = $input.all().map((item, i) => {\n  const c = $('Klaviyo: create draft campaign').itemMatching(i).json.data;\n  const p = $('Parse campaign').itemMatching(i).json;\n  return { id: c.id, segment: p.segment, draft_ids: p.draft_ids, subject_a: p.subject_a, subject_b: p.subject_b };\n});\nconst ids = [...new Set(campaigns.flatMap((c) => c.draft_ids))];\nreturn ids.map((draft_id) => ({ json: {\n  draft_id,\n  status: 'in Klaviyo (draft)',\n  klaviyo_campaign_ids: campaigns.filter((c) => c.draft_ids.includes(draft_id)).map((c) => c.id).join(', '),\n  notes: campaigns.map((c) => `${c.segment}: A \"${c.subject_a}\" / B \"${c.subject_b}\"`).join(' | '),\n} }));"
      },
      "id": "287cc075-b813-41c5-a7f5-449240d13fb7",
      "name": "Link drafts to calendar rows",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        720,
        580
      ]
    },
    {
      "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": "={{ $json.status }}",
            "klaviyo_campaign_ids": "={{ $json.klaviyo_campaign_ids }}",
            "notes": "={{ $json.notes }}"
          },
          "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": "klaviyo_campaign_ids",
              "displayName": "klaviyo_campaign_ids",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "notes",
              "displayName": "notes",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            }
          ]
        },
        "options": {}
      },
      "id": "5b21b4df-d969-48c1-ae09-4acab50f288b",
      "name": "Update calendar rows",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        960,
        580
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "credGSheets00001",
          "name": "Google Sheets (client)"
        }
      }
    },
    {
      "parameters": {
        "chatId": "REPLACE_WITH_OWNER_CHAT_ID",
        "text": "={{ $('Parse campaign').all().length }} drafts ready in Klaviyo ({{ $(\"Group this week's blocks by segment\").first().json.week }}):\n{{ $('Parse campaign').all().map(c => '- ' + c.json.segment + ': A \"' + c.json.subject_a + '\" / B \"' + c.json.subject_b + '\"').join('\\n') }}\n\nNothing has been sent. Review, pick a subject and schedule in Klaviyo.",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "id": "1213be5b-33f8-4e8b-815e-f7711e3317a3",
      "name": "Telegram: drafts ready",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        1200,
        580
      ],
      "credentials": {
        "telegramApi": {
          "id": "credTelegram0001",
          "name": "Telegram bot (client)"
        }
      },
      "executeOnce": true
    }
  ],
  "connections": {
    "Every Monday 07:30": {
      "main": [
        [
          {
            "node": "Read email blocks",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read email blocks": {
      "main": [
        [
          {
            "node": "Group this week's blocks by segment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Group this week's blocks by segment": {
      "main": [
        [
          {
            "node": "Assemble campaign with Claude",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Assemble campaign with Claude": {
      "main": [
        [
          {
            "node": "Parse campaign",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse campaign": {
      "main": [
        [
          {
            "node": "Klaviyo: create template",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Klaviyo: create template": {
      "main": [
        [
          {
            "node": "Klaviyo: create draft campaign",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Klaviyo: create draft campaign": {
      "main": [
        [
          {
            "node": "Klaviyo: attach template",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Klaviyo: attach template": {
      "main": [
        [
          {
            "node": "Link drafts to calendar rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Link drafts to calendar rows": {
      "main": [
        [
          {
            "node": "Update calendar rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update calendar rows": {
      "main": [
        [
          {
            "node": "Telegram: drafts ready",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": []
}
