Every social scheduler shows you a green tick when your post goes out. A
surprising number of them are guessing. I spent ten weeks building the one
that isn't — and then built an entire publishing operation on top of that
single refusal.
By Cristian ThomasSolo build, with agents02 Jun – 09 Aug 2026~14 min read
710commits
10weeks
54workstreams
13channels
61agent tools
95migrations
77.5klines
01 · The failure that started it
A post that went out twice, and a dashboard that said once
Here is the failure mode that made me build this. A scheduler fires. It
calls the platform API. The post is created. The response never comes back —
a timeout, a dropped connection, a function that died mid-flight. The job is
marked failed. A retry runs. The post is created
again.
Now you have two identical posts on your feed and a dashboard that says the
publish failed. Every component behaved reasonably. The result is still a lie.
The tools I was paying for did some version of this. More often it was the
quieter one, where a post silently failed on three of five channels and the
row went green anyway — because the row's status was a single column and the
truth had five values. I'd find out days later, from a client.
A status that can be wrong is worse than no status. It teaches you to stop
reading it.
The principle the whole product is built on
So I started from the other end. Not "what features should a social scheduler
have" — that list is well known and every competitor has it. Instead:
what would a system look like if it were structurally incapable of
telling you something false about your own content? That question
had consequences all the way down, and answering it honestly is what took ten
weeks.
What came out is Instafeeder. Everything below is the real
thing running against a live workspace — my own company's account, real
posts, real creative, real publish state, nothing staged.
02 · The surface
One workspace, every stage of the work, no tab-hopping
Content work has stages — draft, review, ready, scheduled, posted — and most
tools give you one view and make you filter. Instafeeder gives the same set
of posts six lenses and lets the work choose: a grid when
you're thinking visually, a board when you're moving things
through review, a list when you're auditing, a
gallery when you're checking how the feed will actually
look, a calendar and a scheduler when
you're thinking in time.
app.instafeeder.com/w/interstaff/posts
Workspace
Posts
HomeGridGalleryPostedArchived
30 posts
IG — The delegation mistake (jab carousel)
07 Aug07 Aug · 16:00
Posted
4
B5 · Social — Outbound & TCPA
06 Aug06 Aug · 17:00
Posted
B6 · Social — Answering Service Cost
10 Aug10 Aug · 17:00
Scheduled
[W1 Wed] Manny LI — The Intake Autopsy
03 Aug
Review
10
B1 · Social — Legal Answering Service
29 Jul29 Jul · 17:00
Posted
6
IG — 5 upgrades that cost $0 (jab carousel)
03 Aug03 Aug · 16:00
Posted
P1 — The Shift (attorney launch reel)
26 Aug
Draft
Being a lawyer: 40% practicing law, 60% explaining why the thing they saw on TikTok doesn't apply to their case.
420
Threads — Lawyer 40/60 (jab)
29 Jul29 Jul · 20:00
Posted
Posts · Grid lens, real workspace, every card showing the creative its
post actually owns. The 3px top edge is the status colour, and where a post
has live jobs a second band bevels in underneath it — so publish state is
readable before you've read a word. Two cards carry only a bare date
with no time beside it: Manny's carousel and the 26 August draft have a
planner slot and no queue, and the missing timestamp is the signal.
Manny's date was 3 August; it has been and gone, and nothing went out, because
a date is not a job. The heart is total engagement across channels, and the
month's best post is a text-only Threads jab, which renders as a miniature
Threads card rather than an empty grey box.
03 · The hardest idea in the product
Two schedules, deliberately
A post carries two different dates, and keeping them apart is the
design decision I'd defend hardest.
The first is the planner date — where the post sits in your
calendar. It's a plan. Dragging it changes nothing in the world. The second
is the publish queue — actual jobs, per channel, that a cron
executes. Only the queue makes anything go live.
Every tool I've used collapses these into one field, and it's why calendars
lie. You drag a post to Thursday, the calendar shows Thursday, and it goes
out Tuesday because the queue was never touched. Or worse: it shows as
scheduled because it has a date, and it will never publish at all.
Separating them costs a concept to learn. It buys a calendar that is never
wrong. Everywhere the two could be confused — the API, the agent tools, the
post header — they're named apart, because a caller who can write the queue
but can't see it will queue the same post twice.
app.instafeeder.com/w/interstaff/scheduler
Scheduler
The queue
4 queued · 6 channels connected · Europe/Madrid
21h
next out
B6 · Social — Answering Service Cost
Facebook · 17:00X · 17:00Threads · 17:00
3 jobs pending
Sun 03 Augpassed
[W1 Wed] Manny LI — The Intake Autopsy
planner date only · 0 publish jobs · did not go out
Never queued
Fri 08 Augnothing queued
Sat 09 Aug
20:00 now
Sun 10 Aug
B6 · Social — Answering Service Cost
17:00Queued
11 – 18 Augeight empty days
Wed 19 Aug
IG — Answered: Missed to Booked (HOOK)
rescheduled from 16:00 · that job kept as canceled
17:00Queued
Wed 26 Aug
P1 — The Shift (attorney launch reel)
planned for this date · not queued
—Planned only
Scheduler, real queue. Every day gets a tick whether or not anything
publishes on it, so the eight-day gap between 11 and 18 August is literally
empty space rather than days that quietly aren't drawn. Two entries carry the
distinction: the 26 August draft is Planned only — it has a date and
will not publish — and the 3 August carousel is that same condition after the
fact, still saying so instead of pretending.
04 · The rule that keeps it honest
A blocker is exactly what the server refuses. Nothing more.
Thirteen platforms have thirteen sets of rules, and they contradict each
other. YouTube needs a video and a title. X cuts at 280 characters, Threads
at 500. Instagram feed captions aren't clickable, so a link in one is simply
unreachable — which matters enormously when the whole post exists to drive a
click.
Most tools discover this at publish time, the worst possible moment: you've
stopped thinking about the post and the failure arrives as an email at 6am.
So the knowledge moved to where the work happens — a readiness panel in the
editor that answers "will this go out" while it's still cheap to fix.
The panel has one rule, and it's why it's worth trusting: it does not
re-implement the platform rules. It calls the same function the schedule
button calls. It cannot say "blocked" for something that would in fact
schedule, and it cannot stay quiet about something the server will refuse.
Everything else is a warning — and a warning says what will actually happen,
not a vague "check this".
Try it. These are the six channels this workspace really
has connected, and the rules are the real ones.
Blockers come from channelScheduleError() — the same
call the Schedule action makes. Warnings never block.
Post editor · Readiness panel — live, not a picture. Switch YouTube on
with an image attached and it blocks, because the server would refuse it; swap
to video and the block clears on its own. The Instagram warning never blocks
anything, but this post's whole job is a link to the blog, and on Instagram
that link is unreachable — worth knowing before you publish, not after.
05 · Composition
One post, thirteen channels, and none of them the same
"Cross-posting" usually means broadcasting one caption everywhere and
accepting that it reads badly on four platforms. Instafeeder treats a post as
a single idea with per-channel expression: its own caption, hashtags, first
comment, title, crop — and its own creative.
The screenshot below is a real post. The master caption is
428 characters and carries a UTM-tagged link. X's
limit is 280, so X has an override: a tighter 248-character rewrite with
its own UTM source, so the traffic it drives is attributable
separately. YouTube carries a title, because it's the only channel that has
one. None of that is possible if a post is a single text field.
Posts also carry variants — a real A/B set, each with its own
media, aspect ratio and video cover — and different variants can go to
different channels at different times from the same post. Only the active
variant publishes, and you can push a new thumbnail onto an already-live
YouTube video, which is the actual A/B move nobody lets you make.
app.instafeeder.com/w/interstaff/post/8b956593
← Back to Grid
B5 · Social — Outbound & TCPA
Posted · 3 of 3
MasterFacebookX overrideThreadsYouTube titleInstagram · off
X caption — overrides the master
Warm follow-up can win back dead leads — call the wrong way and it's
TCPA trouble. A plain-English guide for firms →
interstaff.ai/blog/automated-outbound-calling-legal-tcpa?utm_source=x&utm_medium=social
248 / 280
master would be 428 — over by 148
Master caption · used by Facebook and Threads
Warm follow-up calls can win back leads you thought were dead — but
call the wrong way and you're in TCPA trouble. The rules are specific,
and they're written down. …
utm_source=social
Instagram is switched off for this post. Its captions aren't
clickable, and the entire point of this one is the link. The first
comment is clickable on Facebook, LinkedIn and YouTube — that
path is plumbed.
Creative · 4:5
Placements · all live
Facebook
✓ permalink
X
✓ permalink
Threads
✓ permalink
A placement is only "succeeded" once it can prove it — every success here carries a live link.
Post editor, real post. One idea, four channels, one caption override
written specifically because X's 280-character limit would have truncated the
link out of the master. Instagram is deliberately off — the post is a
link, and Instagram captions don't carry one.
06 · Measurement
Numbers that refuse to invent zeros
Same principle, pointed at reporting. When a platform doesn't return a
metric, most dashboards render 0. That's a fabrication, and it
compounds: zeros drag averages down, make a good week look flat, and quietly
teach you the wrong lesson about your own content.
Instafeeder renders —. A dash means "not reported", a zero
means zero, and the two are never merged. The panel below is this
workspace's real 30-day shape — same posts, same channels, same
reported-versus-not pattern — with the engagement volumes scaled up for
legibility at screenshot size. Every rate is recomputed from the two numbers
sitting beside it, which is why they all still agree.
The coverage grid is the part I'd point at first. Five platforms, nine
metrics, and they agree on almost nothing — Threads reports shares but not
reach, Instagram reports reach and saves but not shares, X reports only
impressions and clicks, YouTube reports neither impressions nor reach. Any
dashboard showing you one tidy comparable table across those five is making
something up.
app.instafeeder.com/w/interstaff/analytics
Analytics
Last 30 days
26 posts measured · snapshot 09 Aug 04:17
Impressions
15,560
summed only where reported
Interactions
530
3 of 5 channels report these
Engagement rate
3.41%
recomputed, never averaged
Reach
—
per channel only · never summed
LinkedIn followers
12,786
▲ 325 in 23 days
Posts ledgerOverviewChannelsReport
Post
Channel
Published
Impr.
Reach
React.
Rate
Captured
Threads — Lawyer 40/60 (jab)
Threads
29 Jul 20:00
7,160
7,160
420
5.87%
17 captures
Threads — Calendar doesn't lie (jab)
Threads
01 Aug 16:00
1,470
1,470
62
4.22%
2 captures
IG — Calendar doesn't lie (jab)
Instagram
05 Aug 16:00
1,210
1,050
48
3.97%
not yet captured
B5 · Outbound & TCPA
Threads
06 Aug 17:00
100
100
4
4.00%
not yet captured
B5 · Outbound & TCPA
X
06 Aug 17:00
—
—
0
—
not yet captured
B5 · Outbound & TCPA
Facebook
06 Aug 17:00
—
—
0
—
not yet captured
[YT] V1 — The 4 Places Law Firms Lose Clients
YouTube
28 Jul 19:34
—
—
3
—
19 captures
Coverage — which metrics each channel actually reports
Channel
Impr.
Reach
Views
Likes
Comm.
Shares
Saves
Clicks
Follows
Threads
●
—
●
●
●
●
—
—
—
Instagram
●
●
●
●
—
—
●
—
—
Facebook
●
●
—
—
—
—
—
—
—
X
●
—
—
—
—
—
—
●
—
YouTube
—
—
●
●
—
—
—
—
—
Reach is deliberately absent from every cross-channel total: it counts unique people, so
adding it across channels double-counts anyone who saw more than one. It appears per channel only.
Analytics · Posts ledger and coverage grid. The structure, channels,
capture ages and coverage map are this workspace's real 30-day snapshot;
engagement volumes are illustrative. One row per placement, not per
post — the same post on Threads and on X is two different results. Facebook
and X return no impression figure for the TCPA post, so the ledger shows a
dash and the engagement rate stays blank rather than being computed from a
number nobody supplied.
07 · The part I think changes things
Your social team is a chat window
Instafeeder ships a remote MCP server — 61 tools that connect
Claude directly to your workspace so you can run the operation by talking to
it. Not a chatbot bolted on the side. The agent is a first-class client of the
same product. Every figure in the screenshots above was pulled through it.
Three things make it more than a demo. First, the tools call the same
core functions the UI calls — nothing is re-implemented for the
agent, so the app and the agent can't drift apart or enforce different rules.
Second, authorization is identical: an OAuth token is
exchanged for a short-lived user JWT, so every read and write goes through the
same row-level security as the browser. Claude can only touch the workspaces
you belong to, limited by your role. There's no admin backdoor for the agent,
because there's no separate path.
Third — the part I thought hardest about — some tools are marked
destructive, and the set isn't what you'd guess. It isn't just
the deletes. It's the ad budget controls, because the risk of changing a budget
isn't a lost row, it's spend that keeps charging. And some things are
deliberately not exposed at all: connecting channels (OAuth belongs in
a browser), deleting a workspace, anything that sends a direct message.
claude.ai — connector: instafeeder
CT
Put the TCPA guide out to Facebook, X and Threads at 5pm. Same caption,
but X needs its own — the master won't fit.
One note before I queue: the warning is Instagram —
its captions aren't clickable, and this post exists to drive a
click. I've left Instagram switched off. Facebook, X and Threads are
clear.
MCP connector — a representative session against the real workspace, with
the real tool names. The agent surfaced the Instagram link problem before
queueing rather than after, and the budget change stops for confirmation because
destructiveHint covers spend, not just deletion.
What I keep coming back to: the readiness rules, the two-schedule distinction,
the refusal to invent zeros — none of it was built for the agent. But
an agent is the harshest possible consumer of a dishonest status, because it
acts on it immediately and at speed. Building for truthfulness first is what
made the product agent-safe. I didn't plan that. It's the most useful accident
in the whole build.
08 · The build
Ten weeks, one person, and a register that stopped the agents colliding
710 commits between 2 June and 9 August. 89 pull requests squashed into
main, the most recent #250. 558 TypeScript files, 77,565 lines,
95 database migrations. Next.js 16, React 19, Supabase with row-level security
throughout, Backblaze B2 for media, Vercel for everything else.
I built it solo, running several Claude Code agents in parallel. That works
right up until two of them edit the same file, and then it stops working badly.
The fix was boring and effective: a git-tracked program register.
Every workstream claims the file globs it intends to touch and commits that
claim before writing code. Two sessions claiming the same path collide
as a merge conflict instead of silently overwriting each other. Each agent works
in its own worktree on its own branch.
The register has a companion I'd recommend to anyone: a roadmap of what
is deliberately not built, with reasons. Nothing in it is half-finished
— if it's listed, no code for it exists. Applying the product's own honesty
principle to its own documentation is what kept a ten-week solo build legible.
WS-013Publish idempotency — a lost response is no longer a false failureshipped
WS-014Unpublish per channel, and honest statusshipped
WS-031Publish sweep — a lost response recovers instead of lyingshipped
WS-042Logging, audit and status platform — with a verifiable seal chainshipped
WS-044Platform guards — worker pre-flight and editor readinessshipped
WS-049Link at publish time, false-success repairshipped
WS-054A partly-published post is shown honestly everywhereshipped
WS-052Analytics V3 — web and social, honestly joinedplanned
Eight of fifty-four rows from docs/PROGRAM.md. Fifty-two have shipped. I
named each workstream after the lie it removed, which is what made the register
readable a month later.
09 · What it's worth
What changes when the status is trustworthy
The benefits aren't the feature list — every scheduler has a calendar. They come
from the one property a feature list can't show you. One number from the live
workspace makes it concrete: 38 placements succeeded, and all 38 carry
a permalink. Success and provable success are the same set. That
equality is the product.
Outcome
You stop checking the feeds
People manually verify their own posts because they've been burned by a green
tick. When partial failure is a first-class state and a lost response recovers
instead of lying, the dashboard becomes the source of truth again and the
daily audit ritual disappears.
Outcome
Failures surface in minutes, not from a client
Expired connections, refused channels and stalled jobs are caught by a
pre-flight check, a sweep and an alert — before the content goes out, or
within the hour after. Nobody finds out from a customer.
Outcome
Thirteen channels stop costing thirteen workflows
Per-channel captions, hashtags, first comments, titles, crops and creative
variants live on one post. The work scales with ideas, not with the number of
platforms you're on.
Benefit
Reporting you can hand over unedited
A share link with no login, one snapshot, dashes where the platform said
nothing. Nothing in it has been smoothed, so there's no gap between the report
and the truth to explain. That's a much shorter meeting.
Benefit
The calendar is a plan, and it stays one
Because planning and publishing are different objects, you can move things
around freely without wondering whether you just changed what goes live — and
a post that will never publish can't hide behind a date.
Disruption
An operator who doesn't need the UI
61 tools under exactly the same permissions as the browser. Brief it in a
sentence and it composes, checks readiness, queues per channel — and stops for
confirmation on anything that moves money.
Disruption
Category economics, rewritten
Publishing, scheduling, media, approvals, tasks, analytics, ads, audit and an
agent API. Ten weeks, one person. That used to be a funded team and two years,
and that gap is the actual story.
Disruption
Honesty as a moat
Any competitor can copy a calendar in a sprint. Retrofitting truthful state
means changing what a status is — a schema change, a job model, and
every surface that reads it. That is not a sprint.
10 · What isn't built
The part most launch posts leave out
Instafeeder is in beta and there are real gaps, all written down. Web analytics
aren't joined to social yet — that's next, and the spec already admits the hard
part: at this posting cadence almost any traffic spike has several candidate
posts, so ambiguity is the normal case and the design has to lead with it rather
than discover it later.
Seventy-nine error paths still log only to ephemeral platform logs instead of the
event system. Audit-chain verification is a script rather than a screen. Three
historical placements will never get a permalink — one post the provider insists
never published, and two from a workspace whose key returns 403. They're listed
by name in the roadmap, because a known unfixable is information and a quiet gap
is a lie of a different kind.
That's the whole thesis, applied to the thing itself. A product that tells you
the truth about your posts should be able to tell you the truth about its own
edges. If it can't do the second, I wouldn't believe it about the first.
Build the thing that refuses to guess. Everything else is a feature list.
Instafeeder · a social publishing command center · beta
Next.js 16 · React 19 · Supabase · Backblaze B2 · Vercel · MCP
Written by the person who built it, August 2026. Screenshots are the live Interstaff workspace — real posts,
real creative, real publish state, captured 09 August 2026. Engagement volumes
are illustrative; every derived rate is recomputed from the two numbers beside it.