{
  "name": "Generate a week of social media posts with OpenAI and schedule them via PostFast",
  "nodes": [
    {
      "id": "sticky-main",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        16,
        16
      ],
      "parameters": {
        "width": 544,
        "height": 816,
        "content": "## Generate a week of social media posts with OpenAI and schedule them at data-backed best times via PostFast\n\n### How it works\n\nRuns every Monday morning (or on demand), pulls your connected social accounts straight from PostFast so there are no IDs to copy, then OpenAI writes a week of posts in your brand voice. Each post is assigned a proven high-reach time slot based on PostFast's published analysis of 22,890 real Instagram posts and 13,758 TikTok posts (postfa.st/blog/best-time-to-post-on-instagram). Posts are scheduled through PostFast and publish automatically.\n\n### Setup steps\n\n- Install the verified PostFast community node (n8n Cloud: enable Verified Community Nodes in the Admin Panel; self-hosted: install n8n-nodes-postfast).\n- Connect your PostFast credentials (API key from Settings > API) on both PostFast nodes.\n- Connect your OpenAI credentials on the OpenAI node.\n- Update Configure brand and topics: brand name, voice, topics, posts per week and platform filter.\n- Run once manually, review the scheduled posts in your PostFast dashboard, then activate.\n\n### Customization\n\nChange the cadence, tone, post length or platform filter. The default filter targets text-friendly networks: Instagram, TikTok, Pinterest and YouTube require media, so pair them with an AI image workflow instead."
      },
      "typeVersion": 1
    },
    {
      "id": "sticky-step1",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        592,
        32
      ],
      "parameters": {
        "color": 7,
        "width": 448,
        "height": 400,
        "content": "## Start and configure\n\nTriggers every Monday morning and sets the brand, voice, topics, posts per week and platform filter for the run."
      },
      "typeVersion": 1
    },
    {
      "id": "sticky-step2",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1104,
        32
      ],
      "parameters": {
        "color": 7,
        "width": 800,
        "height": 400,
        "content": "## Fetch accounts and write the week\n\nThe PostFast node lists your connected accounts, a filter keeps the platforms you chose, then OpenAI writes the whole week as strict JSON."
      },
      "typeVersion": 1
    },
    {
      "id": "sticky-step3",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1968,
        32
      ],
      "parameters": {
        "color": 7,
        "width": 464,
        "height": 400,
        "content": "## Best-time scheduling\n\nEach post gets next week's strongest slot from PostFast's study of 36,000+ real posts, then every selected account gets its own scheduled copy."
      },
      "typeVersion": 1
    },
    {
      "id": "trigger-monday",
      "name": "Every Monday morning",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        656,
        240
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                1
              ],
              "triggerAtHour": 8
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "set-config",
      "name": "Configure brand and topics",
      "type": "n8n-nodes-base.set",
      "position": [
        864,
        240
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "a1",
              "name": "brandName",
              "type": "string",
              "value": "YOUR BRAND NAME"
            },
            {
              "id": "a2",
              "name": "brandVoice",
              "type": "string",
              "value": "friendly, practical, no hype, first person"
            },
            {
              "id": "a3",
              "name": "topics",
              "type": "string",
              "value": "topic one, topic two, topic three"
            },
            {
              "id": "a4",
              "name": "postsPerWeek",
              "type": "number",
              "value": 5
            },
            {
              "id": "a5",
              "name": "platformFilter",
              "type": "string",
              "value": "X, LINKEDIN, FACEBOOK, THREADS, BLUESKY, TELEGRAM"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "pf-accounts",
      "name": "Get connected accounts (PostFast)",
      "type": "n8n-nodes-postfast.postFast",
      "position": [
        1168,
        240
      ],
      "parameters": {
        "resource": "socialAccount",
        "operation": "getAll"
      },
      "typeVersion": 1
    },
    {
      "id": "code-accounts",
      "name": "Select target accounts",
      "type": "n8n-nodes-base.code",
      "position": [
        1392,
        240
      ],
      "parameters": {
        "jsCode": "// Keep only connected accounts on the platforms chosen in the Configure node.\nconst cfg = $('Configure brand and topics').first().json;\nconst wanted = String(cfg.platformFilter || '').split(',').map(s => s.trim().toUpperCase()).filter(Boolean);\nconst accounts = $input.all().map(i => i.json);\n\nconst selected = accounts.filter(a => {\n  const status = String(a.connectionStatus || 'CONNECTED').toUpperCase();\n  const platform = String(a.platform || '').toUpperCase();\n  return status === 'CONNECTED' && (wanted.length === 0 || wanted.includes(platform));\n});\n\nif (selected.length === 0) {\n  throw new Error('No connected accounts matched the platform filter. Check the Configure node and your PostFast connections.');\n}\n\nreturn [{ json: {\n  accountIds: selected.map(a => a.id ?? a.socialMediaId),\n  accountList: selected.map(a => `${a.platform}: ${a.name ?? a.username ?? a.id}`).join(', ')\n} }];"
      },
      "typeVersion": 2
    },
    {
      "id": "openai-write",
      "name": "Write the week's posts",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        1616,
        240
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini",
          "cachedResultName": "gpt-4o-mini"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "content": "=You are a social media copywriter for {{ $('Configure brand and topics').first().json.brandName }}. Voice: {{ $('Configure brand and topics').first().json.brandVoice }}.\n\nWrite {{ $('Configure brand and topics').first().json.postsPerWeek }} distinct social media posts about these topics: {{ $('Configure brand and topics').first().json.topics }}.\n\nRules: each post 60 to 180 words, hook in the first line, 2 to 4 relevant hashtags at the end, no emoji overload, each post must stand alone.\n\nReturn ONLY valid JSON in this exact shape: {\"posts\": [{\"text\": \"full post text with hashtags\"}]}"
            }
          ]
        },
        "jsonOutput": true
      },
      "typeVersion": 1.8
    },
    {
      "id": "code-slots",
      "name": "Assign data-backed time slots",
      "type": "n8n-nodes-base.code",
      "position": [
        2032,
        240
      ],
      "parameters": {
        "jsCode": "// Assign each post to next week's strongest slot, one item per post per account.\n// Slots (UTC) from PostFast's analysis of 22,890 IG + 13,758 TikTok real posts:\n// weekday 17:00-19:00 UTC band leads on reach; Saturday 14:00 UTC is the standout.\n// Source: postfa.st/blog/best-time-to-post-on-instagram\nconst raw = $input.first().json;\nlet parsed = raw.message?.content ?? raw.output ?? raw;\nif (typeof parsed === 'string') parsed = JSON.parse(parsed);\nconst posts = parsed.posts || [];\nif (posts.length === 0) throw new Error('OpenAI returned no posts. Check the prompt output.');\n\nconst accountIds = $('Select target accounts').first().json.accountIds;\n\n// [dayOfWeek(0=Sun..6=Sat), hourUTC]\nconst SLOTS = [[2, 17], [3, 18], [4, 19], [5, 19], [6, 14], [1, 17], [0, 17]];\n\nfunction nextSlotDate(dow, hour, after) {\n  const d = new Date(after);\n  d.setUTCHours(hour, 0, 0, 0);\n  while (d.getUTCDay() !== dow || d <= after) d.setUTCDate(d.getUTCDate() + 1);\n  return d;\n}\n\nlet cursor = new Date(Date.now() + 60 * 60 * 1000); // at least 1h ahead\nconst items = [];\nposts.forEach((p, i) => {\n  const [dow, hour] = SLOTS[i % SLOTS.length];\n  const when = nextSlotDate(dow, hour, cursor);\n  cursor = when;\n  for (const accountId of accountIds) {\n    items.push({ json: {\n      socialMediaId: accountId,\n      content: p.text,\n      scheduledAt: when.toISOString()\n    } });\n  }\n});\n\nreturn items;"
      },
      "typeVersion": 2
    },
    {
      "id": "pf-create",
      "name": "Schedule posts via PostFast",
      "type": "n8n-nodes-postfast.postFast",
      "position": [
        2256,
        240
      ],
      "parameters": {
        "posts": {
          "post": [
            {
              "content": "={{ $json.content }}",
              "scheduledAt": "={{ $json.scheduledAt }}",
              "socialMediaId": "={{ $json.socialMediaId }}"
            }
          ]
        },
        "status": "SCHEDULED",
        "controls": {},
        "resource": "socialPost",
        "operation": "create",
        "approvalStatus": "APPROVED"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "connections": {
    "Every Monday morning": {
      "main": [
        [
          {
            "node": "Configure brand and topics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Select target accounts": {
      "main": [
        [
          {
            "node": "Write the week's posts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Write the week's posts": {
      "main": [
        [
          {
            "node": "Assign data-backed time slots",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configure brand and topics": {
      "main": [
        [
          {
            "node": "Get connected accounts (PostFast)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Assign data-backed time slots": {
      "main": [
        [
          {
            "node": "Schedule posts via PostFast",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get connected accounts (PostFast)": {
      "main": [
        [
          {
            "node": "Select target accounts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}