We stopped BEUP's videos from double-posting — by fixing the architecture, not the symptom
BEUP's auto-posting kept putting the same video out twice and firing YouTube + Facebook at the same minute. It had been 'fixed' several times and kept coming back in a new shape. We traced it to a missing invariant — there was no single source of truth for what was scheduled — and rebuilt the publishing spine. The recurring bug is now structurally impossible, and you can finally see the whole schedule in one place.
6-layer thinking chainChuỗi tư duy 6 tầng
"Check again why yesterday and the day before there were a lot of posts on YouTube and Facebook posted at the same time frame. I suppose you already fixed the n8n workflow, but here it is repeating again."
How publishing works now (and why it couldn't before)
Before, the producer fired posts straight into n8n as 'wait-until-time' jobs that no one could see or count — so a second scheduling run, or a one-minute time drift, silently created a duplicate. Now everything routes through one visible ledger, and a single dispatcher is the only thing that publishes.
| Technical termThuật ngữ kỹ thuật | Plain nameTên dễ hiểu | RoleVai trò |
|---|---|---|
beup_cadence.py | Posting rhythm | The single definition of timing: YouTube day 0 (20:00), Instagram +3 days (20:30), Facebook +5 days (08:30). Replaces three conflicting copies. |
Notion 'BEUP Publish Ledger' | The schedule book | One row per clip+platform+day, keyed so duplicates are impossible. Status moves scheduled → claimed → posted. The first place you can actually SEE what is scheduled. |
VID-TICK (cb7mkv6qQCVIFy8A) | The dispatcher | Runs every 5 minutes, reads rows that are due, claims them so they can't fire twice, calls the right publisher, marks them posted. The single owner of timing. |
Dedup Guard (per-day) | One-per-day lock | Backstop inside each publisher: at most one post per platform per day, regardless of minute or which run sent it. |
beup-video-distribute.py (--via ledger) | Scheduler input | Bakes the exact post payload and writes it to the ledger instead of firing a blind webhook. Re-running is a no-op (idempotent). |
Read it left-to-right: you schedule a clip → it lands as one row in the ledger → the dispatcher picks it up when its time comes and publishes it once. The greyed-out bottom path is the old design: posts went straight into n8n as parked jobs nobody could see or count, so two scheduling runs (or a one-minute drift) quietly produced a duplicate.
What we did, in order
Layered decision cardsCác quyết định theo tầng
Artifact mapBản đồ tệp tạo ra
| PathĐường dẫn | WhatLà gì | Who reads itAi dùng |
|---|---|---|
scripts/beup_cadence.py | The single canonical posting rhythm (YT d0 / IG +3d / FB +5d, distinct times) | Producer (and future schedulers) read timing from here |
scripts/beup_ledger.py | Notion ledger helper: idempotent upsert / due-query / claim / mark | Producer writes; migration + audits read |
scripts/beup-video-distribute.py | Producer: new --via ledger path + --cadence flag; bakes payload, upserts a row | Manual + batch scripts; default now ledger mode |
n8n: VID-TICK (cb7mkv6qQCVIFy8A) | The dispatcher — reads due rows, claims, fires the publisher, marks posted | Runs every 5 min, owns all timing |
n8n: VID-05/06/08/09 | Publishers: dedup lock coarsened to per-day; Facebook thumbnail bug fixed | Called only by the dispatcher (or, for now, the draining parked jobs) |
Notion DB 'BEUP Publish Ledger' | The single source of truth for what is scheduled / posted | You can open it to see the whole schedule |
.env (BEUP_PUBLISH_VIA=ledger, NOTION_PUBLISH_LEDGER_DB) | Flips the producer into ledger mode + points it at the ledger | Reversible: set back to webhook to revert |
- Done already: you created the n8n 'Notion BEUP Ledger' credential (the one piece the security layer wouldn't let the AI handle).
- Optional, after 2026-07-06 (once the 21 old posts have all fired): strip the now-unused Wait nodes from the four publishers — pure cleanup, not required.
- Optional: add the --cadence flag inside the /beup-shorts-queue skill if you want the canonical YT/IG/FB day-spread enforced automatically on weekly batches.
- Optional: build a daily 'drift audit' that reads the ledger and alerts if anything looks off before it posts.