{
  "id": "550e8400-e29b-41d4-a716-446655440099",
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "name": "Launch Content Pack \u2014 Digital Product Marketing",
  "tags": [],
  "nodes": [
    {
      "id": "f271241b-b7c8-422a-a32a-9a9317c9e49b",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -352,
        128
      ],
      "parameters": {
        "width": 480,
        "height": 768,
        "content": "## Launch Content Pack \u2014 Digital Product Marketing\n\n### How it works\n\nThis workflow creates a digital product launch content pack from sample product data. It is started manually, passes product details into an OpenAI-powered LLM chain, formats the generated response, and can optionally save the final content to Google Sheets.\n\n### Setup steps\n\n- Configure OpenAI credentials for the OpenAI Chat Model used by the LLM chain.\n- Review and replace the sample product fields in the Sample Product Data node with the actual product details.\n- If saving results, add Google Sheets credentials, configure the target spreadsheet and sheet, then enable or activate the optional Google Sheets node.\n- Run the workflow manually to generate and inspect the formatted content pack.\n\n### Customization\n\nAdjust the sample product fields and LLM prompt to change the product positioning, campaign angle, tone, or content formats. The Google Sheets destination can be changed to another storage or publishing step if needed."
      },
      "typeVersion": 1
    },
    {
      "id": "6ce985fb-0c71-43da-bbd1-e5db1ce3a991",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        208,
        128
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 336,
        "content": "## Prepare product input\n\nStarts the workflow manually and loads sample digital product marketing details that will be used as the source data for content generation."
      },
      "typeVersion": 1
    },
    {
      "id": "20e6937a-33bc-4642-b962-008696c2f094",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        672,
        144
      ],
      "parameters": {
        "color": 7,
        "width": 352,
        "height": 528,
        "content": "## Generate launch copy\n\nUses the LLM chain and its nearby OpenAI chat model to create the launch content pack from the prepared product data."
      },
      "typeVersion": 1
    },
    {
      "id": "50151305-0520-44c1-aa9a-6f38fd308967",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1056,
        128
      ],
      "parameters": {
        "color": 7,
        "width": 448,
        "height": 336,
        "content": "## Format and save output\n\nNormalizes the LLM response into a content pack structure and optionally writes the finished results to Google Sheets once credentials are configured."
      },
      "typeVersion": 1
    },
    {
      "id": "550e8400-e29b-41d4-a716-446655440001",
      "name": "Launch Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        250,
        300
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "550e8400-e29b-41d4-a716-446655440002",
      "name": "Set Product Data",
      "type": "n8n-nodes-base.set",
      "position": [
        470,
        300
      ],
      "parameters": {
        "mode": "raw",
        "options": {},
        "jsonOutput": "{\n  \"productName\": \"Prompt Pack Pro: ChatGPT for Etsy Sellers\",\n  \"tagline\": \"50 copy-paste prompts that write your listings, emails, and social posts \u2014 in your brand voice.\",\n  \"price\": \"$27\",\n  \"productURL\": \"https://your-store.gumroad.com/l/promptpackpro\",\n  \"targetAudience\": \"Etsy sellers who hate writing but need to show up consistently online\",\n  \"brandTone\": \"Warm, direct, no-fluff \u2014 like a smart friend who happens to be a copywriter\"\n}"
      },
      "typeVersion": 3.4
    },
    {
      "id": "550e8400-e29b-41d4-a716-446655440003",
      "name": "Create Launch Content",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        720,
        300
      ],
      "parameters": {
        "text": "={{ \"Product Name: \" + $json.productName + \"\\nTagline: \" + $json.tagline + \"\\nPrice: \" + $json.price + \"\\nProduct URL: \" + $json.productURL + \"\\nTarget Audience: \" + $json.targetAudience + \"\\nBrand Tone: \" + $json.brandTone }}",
        "messages": {
          "messageValues": [
            {
              "type": "SystemMessagePromptTemplate",
              "message": "You are an expert launch copywriter for digital products sold on Gumroad and Etsy.\n\nReturn ONLY valid JSON, no markdown, no code fences, no prose, no explanation. The JSON object must contain exactly these keys: blog_intro, linkedin_post, x_post, instagram_caption, launch_email. Return exactly this json structure:\n\n{\"blog_intro\": \"<200-word benefit-led blog intro that hooks the reader, focuses on outcomes not features, and ends with a clear CTA linking to the product URL>\", \"linkedin_post\": \"<150-200 word professional LinkedIn post; insight-led opening, 2-3 short paragraphs, closes with CTA including the product URL>\", \"x_post\": \"<punchy X/Twitter post under 240 characters total including the product URL; hook-first>\", \"instagram_caption\": \"<100-150 word Instagram caption; warm and vivid tone, 3-5 relevant hashtags at the end, CTA with product URL>\", \"launch_email\": \"<launch email where the first line is exactly 'Subject: [your subject line here]', then a blank line, then 200-250 word benefit-led body, clear CTA with product URL, warm sign-off>\"}\n\nRules:\n- Every single asset MUST include the exact product URL from the input.\n- Lead with benefits and transformation, never with feature lists.\n- Match the stated brand tone exactly.\n- Write real, publish-ready copy \u2014 no placeholders, no filler.\n- Output the JSON object only \u2014 nothing before or after it."
            }
          ]
        },
        "promptType": "define",
        "needsFallback": false,
        "hasOutputParser": false
      },
      "typeVersion": 1.9
    },
    {
      "id": "550e8400-e29b-41d4-a716-446655440004",
      "name": "OpenAI GPT-4 Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        720,
        520
      ],
      "parameters": {
        "model": {
          "mode": "id",
          "value": "gpt-4o-mini"
        },
        "options": {
          "temperature": 0.7,
          "responseFormat": "json_object"
        },
        "responsesApiEnabled": false
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "550e8400-e29b-41d4-a716-446655440005",
      "name": "Format Launch Content",
      "type": "n8n-nodes-base.code",
      "position": [
        1104,
        304
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "// Shape-agnostic reader for the Basic LLM Chain ('Generate Launch Content').\n// The Basic LLM Chain can hand us the data in EITHER of two shapes, depending\n// on the model's Response Format:\n//   PATH A (already-parsed): because OpenAI Chat Model has Response Format =\n//     JSON, the chain auto-parses the model's JSON and exposes the 5 content\n//     keys DIRECTLY at the top level of item.json. There is no `text` field.\n//   PATH B (text-to-parse): the chain returns the raw string under `text`\n//     (or `output` / `response`), which we must JSON.parse ourselves.\n// This code handles both without assuming which one occurred.\nconst item = $input.first();\nconst data = item.json || {};\nconst required = ['blog_intro', 'linkedin_post', 'x_post', 'instagram_caption', 'launch_email'];\n\n// Coerce any value to a string so downstream nodes always get clean text,\n// even if the model returned a number, null, or an object for a field.\nfunction asString(v) { return (v === undefined || v === null) ? '' : String(v); }\n\nlet parsed;\nlet pathTaken;\n\n// PATH A \u2014 the 5 keys are already present at the top level: use them directly.\nif (required.some(function (key) { return key in data; })) {\n  parsed = data;\n  pathTaken = 'already-parsed';\n} else {\n  // PATH B \u2014 fall back to a raw text field and parse it ourselves.\n  const raw = data.text ?? data.output ?? data.response;\n  if (raw === undefined || raw === null || raw === '') {\n    throw new Error(\n      'Could not find content. No top-level content keys and no text/output/response field. ' +\n      'Received keys: ' + JSON.stringify(Object.keys(data))\n    );\n  }\n  // The model may wrap JSON in markdown code fences (```json ... ```). Strip them.\n  let cleaned = String(raw).trim()\n    .replace(/^```(?:json)?\\s*/i, '')  // opening fence\n    .replace(/\\s*```$/i, '')           // closing fence\n    .trim();\n  try {\n    parsed = JSON.parse(cleaned);\n  } catch (e) {\n    throw new Error(\n      'LLM did not return valid JSON. Parse error: ' + e.message +\n      '\\n--- Raw text received ---\\n' + String(raw)\n    );\n  }\n  pathTaken = 'text-to-parse';\n}\n\n// Only fail if a key is genuinely ABSENT. A present-but-empty value is fine.\nconst missing = required.filter(function (key) { return !(key in parsed); });\nif (missing.length > 0) {\n  throw new Error(\n    'Content is missing required key(s): ' + missing.join(', ') +\n    ' (path: ' + pathTaken + '). Received keys: ' + JSON.stringify(Object.keys(parsed))\n  );\n}\n\nreturn [{\n  json: {\n    blog_intro: asString(parsed.blog_intro),\n    linkedin_post: asString(parsed.linkedin_post),\n    x_post: asString(parsed.x_post),\n    instagram_caption: asString(parsed.instagram_caption),\n    launch_email: asString(parsed.launch_email)\n  }\n}];",
        "language": "javaScript"
      },
      "typeVersion": 2
    },
    {
      "id": "550e8400-e29b-41d4-a716-446655440006",
      "name": "Append Launch Content to Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "disabled": true,
      "position": [
        1344,
        304
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [],
          "mappingMode": "autoMapInputData",
          "matchingColumns": []
        },
        "options": {
          "valueInputMode": "USER_ENTERED"
        },
        "resource": "sheet",
        "operation": "append",
        "sheetName": {
          "mode": "name",
          "value": "Sheet1"
        },
        "documentId": {
          "mode": "id",
          "value": "YOUR_SPREADSHEET_ID"
        },
        "authentication": "oAuth2"
      },
      "typeVersion": 4.7
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "550e8400-e29b-41d4-a716-446655440000",
  "connections": {
    "Launch Trigger": {
      "main": [
        [
          {
            "node": "Set Product Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Product Data": {
      "main": [
        [
          {
            "node": "Create Launch Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI GPT-4 Model": {
      "ai_languageModel": [
        [
          {
            "node": "Create Launch Content",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Create Launch Content": {
      "main": [
        [
          {
            "node": "Format Launch Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Launch Content": {
      "main": [
        [
          {
            "node": "Append Launch Content to Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}