wiki-vault/Sources/Dev/instagram-reel-sync-phase4.md
Travis Herbranson 34a268d8dc migration: copy 62 notes from pbs-projects and homelab-projects
Two-layer structure: Sources (raw notes) + Wiki (compile output)
Four domains: Dev (40), Venture (3), Homelab (23), Reference (0)
Includes CLAUDE.md spec, index pages at all levels, compile log

Co-Authored-By: Lovebug <lovebug@herbylab.dev>
2026-05-08 17:21:13 -04:00

7.6 KiB
Raw Blame History

created path project status tags type updated
2026-03-17 Sources/Dev instagram-reel-sync-phase4 active
pbs
n8n
instagram
mysql
automation
project-plan 2026-03-17

Phase 4 Handoff — Instagram Reel Detection & Sync

Context for New Chat Session

This is a handoff document from the previous chat session. Paste this into the new chat along with the standard project context to get up to speed quickly.


Current State — What's Already Built

MySQL Schema (pbs_automation database)

pbs_recipes:

CREATE TABLE IF NOT EXISTS pbs_recipes (
    id           BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    post_id      BIGINT UNSIGNED NOT NULL,
    recipe_title        VARCHAR(500)    NOT NULL,
    recipe_url          VARCHAR(500)    NOT NULL,
    keyword      VARCHAR(100)    NULL,
    created_at   DATETIME        DEFAULT CURRENT_TIMESTAMP,
    updated_at   DATETIME        DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (id),
    UNIQUE KEY (post_id)
);

instagram_posts:

CREATE TABLE IF NOT EXISTS instagram_posts (
    id           BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    reel_id      VARCHAR(50)     NOT NULL,
    pbs_post_id      BIGINT UNSIGNED NULL,
    instragram_caption  TEXT    NULL,
    created_at   DATETIME        DEFAULT CURRENT_TIMESTAMP,
    updated_at   DATETIME        DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (id),
    UNIQUE KEY (reel_id),
    FOREIGN KEY (post_id) REFERENCES pbs_recipes(post_id)
);

Key schema decisions:

  • post_id is nullable in instagram_posts — NULL means unmatched reel
  • reel_id is VARCHAR not BIGINT (Instagram ID integer overflow issue)
  • Normalized — keyword and URL live in pbs_recipes only, JOIN at query time
  • Foreign key constraint means pbs_recipes record must exist before instagram_posts insert

WordPress → pbs_recipes Sync (Complete )

  • WPCode Lite PHP snippet fires on every post save (admin only)
  • Payload: post_id, title, url, tags, categories
  • HMAC signature via X-PBS-Signature header (raw body via X-PBS-Body base64)
  • n8n workflow verifies signature, upserts into pbs_recipes
  • Keyword populated from first WordPress tag if present
  • COALESCE logic: only sets keyword if currently NULL (preserves manual edits)

Instagram Comment Reply Workflow (Complete )

  • Handles incoming Instagram comment webhooks
  • Hash verification → subscribe check → PBS message filter
  • Looks up reel in instagram_posts table
  • Joins to pbs_recipes for keyword and URL
  • Sends DM + public reply on keyword match
  • All dead ends notify Travis via Google Chat

Notify Travis Workflow (Complete )

{
  "title": "Alert title",
  "message": "Detail message",
  "timestamp": "={{ $now.toFormat('MMM dd, HH:mm:ss') }}"
}

Phase 4 Goal — Automate Instagram Reel Detection

The Problem

When Jenny publishes a new Instagram Reel, someone currently has to manually:

  1. Get the WordPress post ID
  2. Add it to the reel caption
  3. Add the reel ID to the instagram_posts table

The Solution — Three-Tier Detection System

Tier 1 — Comment Triggered (real time) When a comment comes in and instagram_posts lookup returns empty:

  • Instead of just notifying Travis, trigger the "Fetch Reel Data" workflow
  • Pass enough context to reply to the original commenter if reel is found
  • First commenter may experience a few seconds delay — acceptable

Tier 2 — Scheduled Poll (every 12 hours)

  • n8n Schedule Trigger polls Instagram media API
  • Catches reels that haven't received a comment yet
  • Proactively keeps table up to date

Tier 3 — Manual Trigger (Content Hub)

  • "Sync Reels" button in PBS Content Hub Flask app
  • Jenny or assistant can trigger on demand after publishing

Queue and Retry Pattern (Option B)

When Tier 1 triggers:

  1. Comment comes in → no record found → store original comment data
  2. Trigger "Fetch Reel Data" workflow with comment context
  3. If reel found → insert into instagram_posts → retry reply to original commenter
  4. If reel not found → Notify Travis with details

Phase 4 Architecture

New Workflow — "Fetch Reel Data"

Triggers:

  • Called from comment reply workflow (Tier 1) with single reel ID
  • Called from Schedule Trigger (Tier 2) with last 20 media items
  • Called from Content Hub webhook (Tier 3) manually

Core loop logic (Loop Over Items node):

[Get reels from Instagram API]
  → [Code Node: extract post_id from caption]
  → [Loop Over Items]
      → [MySQL SELECT: pbs_recipes WHERE post_id = extracted_id]
      → [IF: record found?]
          → TRUE → [MySQL UPSERT: instagram_posts]
          → FALSE → [Notify Travis: Bad/missing post ID in caption]
  → [After loop: if Tier 1 triggered → retry comment reply]

Handoff payload from comment reply workflow (Tier 1):

{
  "resolved_media_id": "the reel ID",
  "comment_text": "original comment text",
  "from_username": "commenter username",
  "comment_id": "needed to post public reply"
}

Instagram Media API Details

No webhook exists for new reel publications — confirmed via Meta docs research. Polling is the only option.

API endpoint:

GET https://graph.facebook.com/v25.0/{ig-user-id}/media
  ?fields=id,caption,media_type,media_product_type,permalink,timestamp
  &limit=20
  &access_token={token}

Key field: media_product_type === "REELS" — do NOT use media_type, it returns "VIDEO" for both regular videos and Reels.

Rate limits: Generous — 4,800 × account impressions/24hrs. Polling every 12 hours is negligible.

Token: Long-lived user access token (60-day expiry). Build monitoring for token expiry — biggest operational risk.


Key n8n Patterns Established

  • Always Output Data on MySQL nodes so IF nodes can evaluate empty results
  • Check field exists (id exists) not length > 0 when Always Output Data is on
  • Google Chat HTTP Request must use "Using Fields Below" not raw JSON
  • Message strings built as single ={{ }} expression with + concatenation
  • $now.toFormat() works in test and live contexts (not $execution.startedAt)
  • MySQL query parameters — do NOT use = prefix, plain comma separated values
  • Loop Over Items — use explicit $('Node Name').first().json not $json inside loops since $json only references immediate previous node
  • Set node before loops — consolidate upstream data needed inside loop so it's always accessible as $json.field
  • reel_id always VARCHAR — Instagram IDs exceed JS safe integer limit

Remaining Work After Phase 4

  • Refactor comment reply workflow to split at Check if we sent into separate "Instagram Reply Handler" workflow (cleaner logs)
  • PBS Content Hub — Phase 5 planning session required before building
  • Authelia SSO for admin tools (Portainer, n8n, Uptime Kuma, phpMyAdmin)
  • Deploy all staging changes to production

Infrastructure Quick Reference

  • Staging: staging.plantbasedsoutherner.com
  • n8n: n8n.plantbasedsoutherner.com
  • phpMyAdmin: phpmyadmin.staging.plantbasedsoutherner.com
  • PBS-API: internal Docker service at http://pbs-api:5000
  • MySQL database: pbs_automation
  • Google Chat alerts: via Notify Travis workflow webhook

Last Updated: March 17, 2026 Maintained by: Travis Project: Plant Based Southerner — Instagram Automation Phase 4