AutomationFlowsEmail & Gmail › Publish Instagram Reels From Notion with Claude Captions and Uploadtourl

Publish Instagram Reels From Notion with Claude Captions and Uploadtourl

ByJitesh Dugar @jiteshdugar on n8n.io

Streamline your content pipeline by bridging Notion and Instagram with a professional "review-before-publish" safeguard. This workflow allows team members to submit content via a simple form, generates AI-optimized captions, and pauses for human approval before going live.

Event trigger★★★★★ complexity34 nodesForm TriggerHTTP RequestN8N Nodes UploadtourlGmail
Email & Gmail Trigger: Event Nodes: 34 Complexity: ★★★★★ Added:

This workflow corresponds to n8n.io template #14658 — we link there as the canonical source.

This workflow follows the Form Trigger → Gmail recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "nodes": [
    {
      "id": "5a92d031-b62b-4f1c-814e-53bff74c56ff",
      "name": "Sticky Nodes 1 to 3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        368,
        1808
      ],
      "parameters": {
        "color": 7,
        "width": 740,
        "height": 580,
        "content": "### \ud83d\udcdd\ud83d\udcd6\u2705 Nodes 1\u20133 \u2014 Form, Read Notion, Validate\n\n**n8n Form \u2013 Submit Reel Request** presents a built-in web form with two fields: Notion Page ID (the page containing the video) and Caption Tone (dropdown: hype / minimal / storytelling). Anyone with the form URL can submit \u2014 no n8n account needed.\n\n**HTTP \u2013 Read Notion Page** calls the Notion API (`GET /v1/pages/{page_id}`) to retrieve the full page properties: Title, Video URL, Cover URL, Description, Tags, and Status. Requires a Notion integration token with read access to the database.\n\n**Code \u2013 Validate & Extract** checks that Video URL is present and that the page Status property is not already Published or Failed. Extracts all fields into a clean flat object and throws a descriptive error early if data is missing \u2014 before any CDN upload or API credit is spent."
      },
      "typeVersion": 1
    },
    {
      "id": "eabcf3a3-8bdd-4f8e-9a45-6c10102dda16",
      "name": "Sticky Nodes 4 to 6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1120,
        1808
      ],
      "parameters": {
        "color": 7,
        "width": 788,
        "height": 580,
        "content": "### \ud83e\udd16\u2b07\ufe0f\u2601\ufe0f Nodes 4\u20136 \u2014 Claude Caption, Download Video, Upload Video\n\n**Code \u2013 Claude AI Caption** sends the page Title, Description, Tags, and selected tone to Anthropic's Messages API (claude-haiku-3-5). Returns a brand-safe caption under 2,200 characters with a CTA and hashtag block. Falls back to a static template if the API call fails, so the workflow never stalls on an AI error.\n\n**HTTP \u2013 Download Video Binary** fetches the raw video file from the Video URL stored in Notion as a binary stream, stored under the property name `videoData`.\n\n**Upload to URL \u2013 Video CDN** pushes the binary to a public CDN. Instagram's Graph API for Reels requires a direct public HTTPS video URL \u2014 it explicitly rejects binary uploads, base64 strings, and private signed URLs."
      },
      "typeVersion": 1
    },
    {
      "id": "97d97282-7a86-4c0d-94df-c69967393dbc",
      "name": "Sticky Nodes 7 to 9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1936,
        1680
      ],
      "parameters": {
        "color": 7,
        "width": 788,
        "height": 676,
        "content": "### \u2b07\ufe0f\u2601\ufe0f\ud83d\udcf8 Nodes 7\u20139 \u2014 Download Cover, Upload Cover, Create IG Container\n\n**HTTP \u2013 Download Cover Binary** fetches the thumbnail image binary from the Cover URL stored in the Notion page. If no Cover URL exists the node is skipped via the `cover_url` empty check in the container body builder.\n\n**Upload to URL \u2013 Cover CDN** generates a second public CDN URL for the cover image. Supplying a specific `cover_url` to Instagram ensures a polished branded thumbnail frame instead of an auto-selected video frame.\n\n**HTTP \u2013 IG Create Reel Container** POSTs to `/v19.0/{ig_user_id}/media` with `media_type=REELS`, `video_url`, `cover_url`, `caption`, and `share_to_feed=true`. Instagram begins asynchronous encoding and returns a `container_id`. No publish happens yet \u2014 the approver must confirm first."
      },
      "typeVersion": 1
    },
    {
      "id": "399303bb-ca48-433c-b741-3a3e9e20befa",
      "name": "Sticky Nodes 10 to 12",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2768,
        1728
      ],
      "parameters": {
        "color": 7,
        "width": 780,
        "height": 580,
        "content": "### \ud83d\udce7\u23f8\ufe0f\ud83d\udd01 Nodes 10\u201312 \u2014 Email Preview, Wait for Approval, Poll Encoding\n\n**Gmail \u2013 Send Preview Email** emails the approver (APPROVER_EMAIL) an HTML preview containing the AI-generated caption, video CDN URL, and two buttons: **Approve** (resumes the webhook wait) and **Reject** (also resumes but routes to the reject branch). The email includes the live Approve URL generated by the Wait node.\n\n**Wait \u2013 Approval Webhook** pauses execution indefinitely until the approver clicks Approve or Reject. n8n generates a unique resume URL per execution \u2014 the Approve button in the email hits this URL to resume. The workflow holds state across the pause without consuming execution time.\n\n**HTTP \u2013 Poll Container Status** checks `status_code` on the container (`IN_PROGRESS` / `FINISHED` / `ERROR`). Called in a loop with a 20-second wait between each attempt for up to 15 tries before the error branch fires."
      },
      "typeVersion": 1
    },
    {
      "id": "41cc9886-61d5-494b-8c19-2a3560a50cdb",
      "name": "Sticky Nodes 13 to 17",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3616,
        1728
      ],
      "parameters": {
        "color": 7,
        "width": 1584,
        "height": 580,
        "content": "### \u2705\ud83d\udce4\ud83d\udd17\ud83d\udccb\ud83d\udcac Nodes 13\u201317 \u2014 Gate, Publish, Permalink, Update Notion, Discord\n\n**IF \u2013 Approved?** checks the query param `action` on the resume URL. `action=approve` routes true; `action=reject` routes false to a Notion update that marks the page Rejected and sends a Discord alert.\n\n**IG \u2013 Publish Reel** calls `/v19.0/{ig_user_id}/media_publish` with `creation_id = container_id`. Returns the live Instagram Post ID. The Reel is now live.\n\n**HTTP \u2013 Fetch Reel Permalink** immediately retrieves `id, permalink, timestamp, media_type` from the Graph API for accurate logging.\n\n**HTTP \u2013 Update Notion Page** PATCHes the original Notion page via the API: sets Status to Published, writes Post ID, Permalink, and Published At as page properties.\n\n**Discord \u2013 Notify Team** POSTs a formatted embed to the team Discord channel via webhook with title, permalink, post ID, and timestamp. No bot setup required \u2014 a simple incoming webhook URL is enough."
      },
      "typeVersion": 1
    },
    {
      "id": "10918b35-34b6-4f6b-bd1d-021c29e3179e",
      "name": "Sticky Error Branch",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3984,
        2416
      ],
      "parameters": {
        "color": 7,
        "width": 660,
        "height": 340,
        "content": "### \u26a0\ufe0f Error Branch \u2014 Encoding Failure & Rejection Handler\n\n**Encoding failure** (after 15 poll attempts): Updates Notion page Status to `Failed` via PATCH, then POSTs a Discord alert with creator, page ID, and last status code so the team can investigate without opening n8n.\n\n**Approval rejected**: Updates Notion page Status to `Rejected`, posts a Discord message so the team knows the submission was declined and can follow up with the submitter."
      },
      "typeVersion": 1
    },
    {
      "id": "b861d568-c7f3-4982-baba-5103dcb41794",
      "name": "n8n Form Submit Reel Request",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        512,
        2112
      ],
      "parameters": {
        "options": {
          "respondWithOptions": {
            "values": {}
          }
        },
        "formTitle": "Publish Instagram Reel",
        "formFields": {
          "values": [
            {
              "fieldLabel": "Notion Page ID",
              "placeholder": "e.g. 1a2b3c4d-5e6f-7890-abcd-ef1234567890",
              "requiredField": true
            },
            {
              "fieldType": "dropdown",
              "fieldLabel": "Caption Tone",
              "fieldOptions": {
                "values": [
                  {
                    "option": "storytelling"
                  },
                  {
                    "option": "hype"
                  },
                  {
                    "option": "minimal"
                  }
                ]
              },
              "requiredField": true
            }
          ]
        },
        "formDescription": "Submit a Notion page to publish as an Instagram Reel. An approver will review before it goes live."
      },
      "typeVersion": 2.2
    },
    {
      "id": "163eff77-09c4-4f6a-b252-80a457247d74",
      "name": "HTTP Read Notion Page",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        720,
        2112
      ],
      "parameters": {
        "url": "=https://api.notion.com/v1/pages/{{ $json['Notion Page ID'] }}",
        "options": {},
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.NOTION_API_KEY }}"
            },
            {
              "name": "Notion-Version",
              "value": "2022-06-28"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "75eb020e-4b90-4f09-b295-a41d8b8408a6",
      "name": "Code Validate and Extract",
      "type": "n8n-nodes-base.code",
      "position": [
        928,
        2112
      ],
      "parameters": {
        "jsCode": "const page = $input.item.json;\nconst props = page.properties || {};\nconst formData = $('n8n Form Submit Reel Request').item.json;\n\nfunction text(prop) {\n  if (!prop) return '';\n  if (prop.type === 'title' && prop.title) return prop.title.map(function(t){ return t.plain_text; }).join('');\n  if (prop.type === 'rich_text' && prop.rich_text) return prop.rich_text.map(function(t){ return t.plain_text; }).join('');\n  if (prop.type === 'url') return prop.url || '';\n  if (prop.type === 'select' && prop.select) return prop.select.name || '';\n  if (prop.type === 'multi_select' && prop.multi_select) return prop.multi_select.map(function(s){ return s.name; }).join(', ');\n  return '';\n}\n\nconst status = text(props['Status']);\nif (status === 'Published') throw new Error('Notion page is already Published. Aborting to prevent duplicate post.');\nif (status === 'Failed')    throw new Error('Notion page is marked Failed. Fix the video before resubmitting.');\nif (status === 'Rejected')  throw new Error('Notion page was previously Rejected.');\n\nconst videoUrl = text(props['Video URL']);\nif (!videoUrl) throw new Error('Notion page has no Video URL property. Cannot publish.');\n\nreturn {\n  notion_page_id: page.id,\n  title:          text(props['Title']),\n  video_url:      videoUrl,\n  cover_url:      text(props['Cover URL']) || '',\n  description:    text(props['Description']) || '',\n  tags:           text(props['Tags']) || '',\n  caption_tone:   formData['Caption Tone'] || 'storytelling',\n  ig_user_id:     $env.IG_USER_ID\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "138738c4-622a-4c3d-aee0-e83e1bde75ec",
      "name": "Code Claude AI Caption",
      "type": "n8n-nodes-base.code",
      "position": [
        1136,
        2112
      ],
      "parameters": {
        "jsCode": "const d = $input.item.json;\nconst tone = d.caption_tone || 'storytelling';\n\nconst toneGuide = {\n  hype:         'Energetic, punchy, 1\u20132 fire emojis, very short sentences. High energy.',\n  minimal:      'Clean and minimal. No emojis. One powerful sentence max.',\n  storytelling: 'Warm, narrative, and personal. Makes the viewer feel something real.'\n};\n\nconst tagBlock = d.tags\n  ? d.tags.split(',').map(function(t){\n      t = t.trim(); return t.startsWith('#') ? t : '#' + t;\n    }).join(' ')\n  : '#reels #instagram #content #fyp';\n\nlet finalCaption;\ntry {\n  const res = await fetch('https://api.anthropic.com/v1/messages', {\n    method: 'POST',\n    headers: {\n      'Content-Type':      'application/json',\n      'x-api-key':         $env.ANTHROPIC_API_KEY,\n      'anthropic-version': '2023-06-01'\n    },\n    body: JSON.stringify({\n      model:      'claude-haiku-4-5',\n      max_tokens: 400,\n      messages: [{\n        role: 'user',\n        content: [\n          'Write an Instagram Reel caption.',\n          'Title: ' + d.title,\n          'Description: ' + (d.description || 'none'),\n          'Tags to include: ' + tagBlock,\n          'Tone: ' + tone + ' \u2014 ' + (toneGuide[tone] || ''),\n          'End with a short CTA to follow or save.',\n          'Max 2200 characters. Output ONLY the caption text, no preamble.'\n        ].join('\\n')\n      }]\n    })\n  });\n  const ai = await res.json();\n  finalCaption = (ai.content && ai.content[0] && ai.content[0].text)\n    ? ai.content[0].text.trim()\n    : null;\n} catch(e) {\n  finalCaption = null;\n}\n\nif (!finalCaption) {\n  finalCaption = (d.title ? d.title + '\\n\\n' : '') +\n    (d.description ? d.description.trim() + '\\n\\n' : '') +\n    'Follow for more.\\n\\n' + tagBlock;\n}\n\nif (finalCaption.length > 2200) finalCaption = finalCaption.substring(0, 2196) + '...';\n\nreturn { ...d, final_caption: finalCaption };"
      },
      "typeVersion": 2
    },
    {
      "id": "fe3e93dc-8042-431c-aa20-cf97b49683fa",
      "name": "HTTP Download Video Binary",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1344,
        2112
      ],
      "parameters": {
        "url": "={{ $json.video_url }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file",
              "outputPropertyName": "videoData"
            }
          }
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "346669ee-91e7-4aa3-a3d2-f0a936c3e868",
      "name": "Upload to URL Video CDN",
      "type": "n8n-nodes-uploadtourl.uploadToUrl",
      "position": [
        1552,
        2112
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "ea2c8b9b-aef3-43cf-ad37-016908523b68",
      "name": "Code Store Video CDN URL",
      "type": "n8n-nodes-base.code",
      "position": [
        1760,
        2112
      ],
      "parameters": {
        "jsCode": "const upload = $input.item.json;\nconst prev = $('Code Claude AI Caption').item.json;\nconst publicVideoUrl = upload.public_url || upload.url || upload.file_url || upload.cdn_url || '';\nif (!publicVideoUrl) throw new Error('Video CDN upload returned no public URL. Keys: ' + JSON.stringify(Object.keys(upload)));\nreturn { ...prev, public_video_url: publicVideoUrl };"
      },
      "typeVersion": 2
    },
    {
      "id": "04823c0b-9e68-4b03-87ea-bd4767b37672",
      "name": "HTTP Download Cover Binary",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1968,
        2112
      ],
      "parameters": {
        "url": "={{ $json.cover_url }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file",
              "outputPropertyName": "coverData"
            }
          }
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "2c34a822-1f79-4ca8-b22c-4c2302ab453d",
      "name": "Upload to URL Cover CDN",
      "type": "n8n-nodes-uploadtourl.uploadToUrl",
      "position": [
        2176,
        2112
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "c2aea54e-eae2-4fbe-8cf8-ced2fc5fd166",
      "name": "Code Merge CDN URLs",
      "type": "n8n-nodes-base.code",
      "position": [
        2384,
        2112
      ],
      "parameters": {
        "jsCode": "const coverUpload = $input.item.json;\nconst videoData = $('Code Store Video CDN URL').item.json;\nconst publicCoverUrl = coverUpload.public_url || coverUpload.url || coverUpload.file_url || coverUpload.cdn_url || '';\nreturn {\n  notion_page_id:   videoData.notion_page_id,\n  title:            videoData.title,\n  ig_user_id:       videoData.ig_user_id,\n  final_caption:    videoData.final_caption,\n  public_video_url: videoData.public_video_url,\n  public_cover_url: publicCoverUrl,\n  retry_count:      0\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "c9c20fdd-44c2-4d1e-bd55-66caf5ceccac",
      "name": "HTTP IG Create Reel Container",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2592,
        2112
      ],
      "parameters": {
        "url": "=https://graph.facebook.com/v19.0/{{ $json.ig_user_id }}/media",
        "method": "POST",
        "options": {},
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "media_type",
              "value": "REELS"
            },
            {
              "name": "video_url",
              "value": "={{ $json.public_video_url }}"
            },
            {
              "name": "cover_url",
              "value": "={{ $json.public_cover_url }}"
            },
            {
              "name": "caption",
              "value": "={{ $json.final_caption }}"
            },
            {
              "name": "share_to_feed",
              "value": "true"
            },
            {
              "name": "access_token",
              "value": "={{ $env.IG_ACCESS_TOKEN }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "4f7cac2f-2a23-41c2-95ee-4a966f42dec3",
      "name": "Gmail Send Preview Email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        2800,
        2112
      ],
      "parameters": {
        "sendTo": "={{ $env.APPROVER_EMAIL }}",
        "message": "=<h2>Reel Pending Your Approval</h2><p><strong>Title:</strong> {{ $('Code Merge CDN URLs').item.json.title }}</p><p><strong>Caption Preview:</strong></p><pre style=\"background:#f4f4f4;padding:12px;border-radius:6px;\">{{ $('Code Merge CDN URLs').item.json.final_caption }}</pre><p><strong>Video:</strong> <a href=\"{{ $('Code Merge CDN URLs').item.json.public_video_url }}\">Preview video \u2192</a></p><br><p><a href=\"{{ $resumeUrl }}?action=approve\" style=\"background:#22c55e;color:white;padding:12px 24px;border-radius:6px;text-decoration:none;font-weight:bold;\">\u2705 Approve & Publish</a>&nbsp;&nbsp;<a href=\"{{ $resumeUrl }}?action=reject\" style=\"background:#ef4444;color:white;padding:12px 24px;border-radius:6px;text-decoration:none;font-weight:bold;\">\u274c Reject</a></p>",
        "options": {},
        "subject": "=\ud83c\udfac Reel Approval Required \u2014 {{ $('Code Merge CDN URLs').item.json.title }}"
      },
      "typeVersion": 2.1
    },
    {
      "id": "e397a7fb-f4a2-49e5-b083-1469b3116e9f",
      "name": "Wait for Approval",
      "type": "n8n-nodes-base.wait",
      "position": [
        2960,
        2000
      ],
      "parameters": {
        "resume": "webhook",
        "options": {}
      },
      "typeVersion": 1.1
    },
    {
      "id": "7981b5dd-ddc0-49c9-b97b-540db45f396f",
      "name": "IF Approved",
      "type": "n8n-nodes-base.if",
      "position": [
        3088,
        2032
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c-approve",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.query.action }}",
              "rightValue": "approve"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "e74d8408-02ab-429a-9c12-52439244c17a",
      "name": "HTTP Notion Mark Rejected",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3248,
        2160
      ],
      "parameters": {
        "url": "=https://api.notion.com/v1/pages/{{ $('Code Merge CDN URLs').item.json.notion_page_id }}",
        "method": "PATCH",
        "options": {},
        "jsonBody": "={\"properties\":{\"Status\":{\"select\":{\"name\":\"Rejected\"}}}}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.NOTION_API_KEY }}"
            },
            {
              "name": "Notion-Version",
              "value": "2022-06-28"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "475a110c-3b16-4a99-a38c-29560923b68f",
      "name": "Discord Notify Rejected",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3392,
        2176
      ],
      "parameters": {
        "url": "={{ $env.DISCORD_WEBHOOK_URL }}",
        "method": "POST",
        "options": {},
        "jsonBody": "={\"content\":\"\u274c **Reel Rejected**\\nTitle: {{ $('Code Merge CDN URLs').item.json.title }}\\nNotion Page: {{ $('Code Merge CDN URLs').item.json.notion_page_id }}\\nThe approver declined this submission.\"}",
        "sendBody": true,
        "specifyBody": "json"
      },
      "typeVersion": 4.2
    },
    {
      "id": "3ee4e041-0963-42b1-bf1a-66af9e63133e",
      "name": "Wait Poll Buffer",
      "type": "n8n-nodes-base.wait",
      "position": [
        3280,
        2016
      ],
      "parameters": {
        "unit": "seconds",
        "amount": 20
      },
      "typeVersion": 1
    },
    {
      "id": "2d3d8e0e-d579-4315-946b-7b457005821c",
      "name": "HTTP Poll Container Status",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3424,
        2016
      ],
      "parameters": {
        "url": "=https://graph.facebook.com/v19.0/{{ $('HTTP IG Create Reel Container').item.json.id }}",
        "options": {},
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "fields",
              "value": "status_code,status"
            },
            {
              "name": "access_token",
              "value": "={{ $env.IG_ACCESS_TOKEN }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "4812b08b-d217-4abd-9cea-03c90eb2f836",
      "name": "Set Increment Retry",
      "type": "n8n-nodes-base.set",
      "position": [
        3840,
        2112
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "r1",
              "name": "retry_count",
              "type": "number",
              "value": "={{ ($('Code Merge CDN URLs').item.json.retry_count || 0) + 1 }}"
            },
            {
              "id": "r2",
              "name": "status_code",
              "type": "string",
              "value": "={{ $json.status_code }}"
            },
            {
              "id": "r3",
              "name": "container_id",
              "type": "string",
              "value": "={{ $('HTTP IG Create Reel Container').item.json.id }}"
            },
            {
              "id": "r4",
              "name": "notion_page_id",
              "type": "string",
              "value": "={{ $('Code Merge CDN URLs').item.json.notion_page_id }}"
            },
            {
              "id": "r5",
              "name": "ig_user_id",
              "type": "string",
              "value": "={{ $('Code Merge CDN URLs').item.json.ig_user_id }}"
            },
            {
              "id": "r6",
              "name": "title",
              "type": "string",
              "value": "={{ $('Code Merge CDN URLs').item.json.title }}"
            },
            {
              "id": "r7",
              "name": "public_video_url",
              "type": "string",
              "value": "={{ $('Code Merge CDN URLs').item.json.public_video_url }}"
            },
            {
              "id": "r8",
              "name": "public_cover_url",
              "type": "string",
              "value": "={{ $('Code Merge CDN URLs').item.json.public_cover_url }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "02b0a34e-e501-418e-9484-3d5e98d7505f",
      "name": "IF Encoding Finished",
      "type": "n8n-nodes-base.if",
      "position": [
        4048,
        2112
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c-finished",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.status_code }}",
              "rightValue": "FINISHED"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "11f6bb1c-990a-452e-9d86-c2771678a9ca",
      "name": "Code Retry or Fail",
      "type": "n8n-nodes-base.code",
      "position": [
        4032,
        2592
      ],
      "parameters": {
        "jsCode": "const d = $input.item.json;\nif (d.retry_count < 15) {\n  return {\n    retry_count:      d.retry_count,\n    status_code:      d.status_code,\n    container_id:     d.container_id,\n    notion_page_id:   d.notion_page_id,\n    ig_user_id:       d.ig_user_id,\n    title:            d.title,\n    public_video_url: d.public_video_url,\n    public_cover_url: d.public_cover_url\n  };\n}\nthrow new Error(\n  'Reel encoding failed after 15 attempts.' +\n  ' Status: ' + d.status_code +\n  ' | Notion: ' + d.notion_page_id +\n  ' | Title: ' + d.title\n);"
      },
      "typeVersion": 2
    },
    {
      "id": "760debb1-e03f-4ca2-90dc-5f4cee557d83",
      "name": "HTTP Notion Mark Failed",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        4256,
        2592
      ],
      "parameters": {
        "url": "=https://api.notion.com/v1/pages/{{ $('Set Increment Retry').item.json.notion_page_id }}",
        "method": "PATCH",
        "options": {},
        "jsonBody": "={\"properties\":{\"Status\":{\"select\":{\"name\":\"Failed\"}}}}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.NOTION_API_KEY }}"
            },
            {
              "name": "Notion-Version",
              "value": "2022-06-28"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "fb298ab6-72ad-483c-92c8-0126fdefaf09",
      "name": "Discord Notify Encoding Failed",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        4464,
        2592
      ],
      "parameters": {
        "url": "={{ $env.DISCORD_WEBHOOK_URL }}",
        "method": "POST",
        "options": {},
        "jsonBody": "={\"content\":\"\u26a0\ufe0f **Reel Encoding Failed**\\nTitle: {{ $('Set Increment Retry').item.json.title }}\\nStatus after 15 attempts: {{ $('Set Increment Retry').item.json.status_code }}\\nNotion page has been marked Failed. Please check the video file.\"}",
        "sendBody": true,
        "specifyBody": "json"
      },
      "typeVersion": 4.2
    },
    {
      "id": "98fde062-dfac-4279-a13e-918574c7e02e",
      "name": "HTTP Fetch Reel Permalink",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        4464,
        2112
      ],
      "parameters": {
        "url": "=https://graph.facebook.com/v19.0/{{ $('IG Publish Reel1').item.json.id }}",
        "options": {},
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "fields",
              "value": "id,permalink,timestamp,media_type"
            },
            {
              "name": "access_token",
              "value": "={{ $env.IG_ACCESS_TOKEN }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "9a2b0d85-c36b-40c4-bea1-af55a656d86d",
      "name": "HTTP Notion Mark Published",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        4672,
        2112
      ],
      "parameters": {
        "url": "=https://api.notion.com/v1/pages/{{ $('Set Increment Retry').item.json.notion_page_id }}",
        "method": "PATCH",
        "options": {},
        "jsonBody": "={\"properties\":{\"Status\":{\"select\":{\"name\":\"Published\"}},\"Post ID\":{\"rich_text\":[{\"text\":{\"content\":\"{{ $('IG Publish Reel1').item.json.id }}\"}}]},\"Permalink\":{\"url\":\"{{ $('HTTP Fetch Reel Permalink').item.json.permalink }}\"},\"Published At\":{\"date\":{\"start\":\"{{ $('HTTP Fetch Reel Permalink').item.json.timestamp }}\"}}}}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.NOTION_API_KEY }}"
            },
            {
              "name": "Notion-Version",
              "value": "2022-06-28"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "d53fc635-a0a6-463b-bd73-c01cc38e3f99",
      "name": "Discord Notify Published",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        4880,
        2112
      ],
      "parameters": {
        "url": "={{ $env.DISCORD_WEBHOOK_URL }}",
        "method": "POST",
        "options": {},
        "jsonBody": "={\"embeds\":[{\"title\":\"\ud83c\udfac Reel Live \u2014 {{ $('Set Increment Retry').item.json.title }}\",\"url\":\"{{ $('HTTP Fetch Reel Permalink').item.json.permalink }}\",\"color\":5763719,\"fields\":[{\"name\":\"Post ID\",\"value\":\"{{ $('IG Publish Reel1').item.json.id }}\",\"inline\":true},{\"name\":\"Published At\",\"value\":\"{{ $('HTTP Fetch Reel Permalink').item.json.timestamp }}\",\"inline\":true},{\"name\":\"Permalink\",\"value\":\"{{ $('HTTP Fetch Reel Permalink').item.json.permalink }}\"}]}]}",
        "sendBody": true,
        "specifyBody": "json"
      },
      "typeVersion": 4.2
    },
    {
      "id": "64c25481-b8c8-41b4-bc4f-72df390d2493",
      "name": "Template Overview1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -160,
        1184
      ],
      "parameters": {
        "width": 500,
        "height": 500,
        "content": "Instagram Reel Publisher \u2014 Notion + Approval Gate + Discord\n\nA content team member fills in an n8n Form with a Notion page ID and caption tone. The workflow reads the video, cover, and metadata from Notion, generates an AI caption via Claude, uploads the video to a CDN, then PAUSES and emails a preview to the approver. Only after the approver clicks the Approve link does the Reel publish. On success it updates Notion and posts to Discord. On encoding failure it alerts Discord and marks the Notion page Failed.\n\nFlow:\nForm \u2192 Read Notion \u2192 Validate \u2192 AI Caption \u2192 Download video \u2192 Upload CDN \u2192 Download cover \u2192 Upload CDN \u2192 Create IG container \u2192 Email preview + wait for approval \u2192 Poll encoding \u2192 Publish \u2192 Fetch permalink \u2192 Update Notion \u2192 Discord notify\n\nRequired env vars:\nIG_USER_ID, IG_ACCESS_TOKEN,\nNOTION_API_KEY, NOTION_DATABASE_ID,\nANTHROPIC_API_KEY, APPROVER_EMAIL,\nDISCORD_WEBHOOK_URL"
      },
      "typeVersion": 1
    },
    {
      "id": "72d2469c-67cf-4190-9e6c-a098e3084f18",
      "name": "IG Publish Reel1",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        4256,
        2112
      ],
      "parameters": {
        "url": "=https://graph.facebook.com/v19.0/{{ $('Set Increment Retry').item.json.ig_user_id }}/media_publish",
        "method": "POST",
        "options": {},
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "creation_id",
              "value": "={{ $('Set Increment Retry').item.json.container_id }}"
            },
            {
              "name": "access_token",
              "value": "={{ $env.IG_ACCESS_TOKEN }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    }
  ],
  "connections": {
    "IF Approved": {
      "main": [
        [
          {
            "node": "Wait Poll Buffer",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "HTTP Notion Mark Rejected",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IG Publish Reel1": {
      "main": [
        [
          {
            "node": "HTTP Fetch Reel Permalink",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait Poll Buffer": {
      "main": [
        [
          {
            "node": "HTTP Poll Container Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait for Approval": {
      "main": [
        [
          {
            "node": "IF Approved",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code Retry or Fail": {
      "main": [
        [
          {
            "node": "Wait Poll Buffer",
            "type": "main",
            "index": 0
          },
          {
            "node": "HTTP Notion Mark Failed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code Merge CDN URLs": {
      "main": [
        [
          {
            "node": "HTTP IG Create Reel Container",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Increment Retry": {
      "main": [
        [
          {
            "node": "IF Encoding Finished",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF Encoding Finished": {
      "main": [
        [
          {
            "node": "IG Publish Reel1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Code Retry or Fail",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Read Notion Page": {
      "main": [
        [
          {
            "node": "Code Validate and Extract",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code Claude AI Caption": {
      "main": [
        [
          {
            "node": "HTTP Download Video Binary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Notion Mark Failed": {
      "main": [
        [
          {
            "node": "Discord Notify Encoding Failed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload to URL Cover CDN": {
      "main": [
        [
          {
            "node": "Code Merge CDN URLs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload to URL Video CDN": {
      "main": [
        [
          {
            "node": "Code Store Video CDN URL",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code Store Video CDN URL": {
      "main": [
        [
          {
            "node": "HTTP Download Cover Binary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail Send Preview Email": {
      "main": [
        [
          {
            "node": "Wait for Approval",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code Validate and Extract": {
      "main": [
        [
          {
            "node": "Code Claude AI Caption",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Fetch Reel Permalink": {
      "main": [
        [
          {
            "node": "HTTP Notion Mark Published",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Notion Mark Rejected": {
      "main": [
        [
          {
            "node": "Discord Notify Rejected",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Download Cover Binary": {
      "main": [
        [
          {
            "node": "Upload to URL Cover CDN",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Download Video Binary": {
      "main": [
        [
          {
            "node": "Upload to URL Video CDN",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Notion Mark Published": {
      "main": [
        [
          {
            "node": "Discord Notify Published",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Poll Container Status": {
      "main": [
        [
          {
            "node": "Set Increment Retry",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "n8n Form Submit Reel Request": {
      "main": [
        [
          {
            "node": "HTTP Read Notion Page",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP IG Create Reel Container": {
      "main": [
        [
          {
            "node": "Gmail Send Preview Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Streamline your content pipeline by bridging Notion and Instagram with a professional "review-before-publish" safeguard. This workflow allows team members to submit content via a simple form, generates AI-optimized captions, and pauses for human approval before going live.

Source: https://n8n.io/workflows/14658/ — original creator credit. Request a take-down →

More Email & Gmail workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Email & Gmail

Stop chasing blurry receipts and manually typing expense data. This workflow creates an intelligent, "snap-and-submit" reimbursement pipeline that hosts photos via UploadToURL, extracts deep data via

Form Trigger, N8N Nodes Uploadtourl, HTTP Request +3
Email & Gmail

🎥 Analyze YouTube Video for Summaries, Transcripts & Content + Google Gemini AI. Uses stickyNote, httpRequest, googleDrive, gmail. Event-driven trigger; 33 nodes.

HTTP Request, Google Drive, Gmail +2
Email & Gmail

This n8n template automates PDF translation into 1 or 2 target languages while maintaining professional formatting. Users submit PDFs via web form and receive translated documents via email with prese

Google Translate, HTTP Request, Form Trigger +2
Email & Gmail

Submit any YouTube, Vimeo, or Zoom webinar URL using a simple form and the workflow handles everything from there. It runs a two-phase pipeline: first identifying the top viral moments in your video w

Form Trigger, HTTP Request, Google Sheets +1
Email & Gmail

Paste any video URL into a simple form and the workflow runs three AI tasks at the same time — summary, viral clip identification, and full transcription. It waits for all three to finish, combines ev

Form Trigger, HTTP Request, Gmail