Case study
A social post every day, that cannot publish without a human
Every business is sitting on data that already says what it should be posting — most viewed, most purchased, most interacted with this week. It just never gets posted. We built Dispatch to close that gap: it connects to the client's own data, turns whatever is performing into a finished post every morning, and then stops and waits for a person to say yes.
- Client
- Digital marketplaceName withheld at client request
- Engagement
- Custom build & handoffStandalone system, client infrastructure
- Delivered
- August 2026Built, tested and handed off
- System
- DispatchBackend, dashboard, CLI, MCP server
The brief
The risk was never a missed post
Four manual jobs, every single day
Choosing an item cleared for promotion, rendering it to three aspect ratios, writing copy that carried the creator's attribution correctly, and posting on schedule. Done by hand, under deadline, the attribution is the step that slips — and publishing a creator's work without the credit their licence requires is a rights problem, not a marketing one.
A short review, and credit that cannot be removed
Dispatch drafts the post overnight, renders every format, writes the copy and locks the creator credit as immutable data. A person opens one screen, checks the licence, tunes the caption and approves. Publishing then happens on schedule, under lease, with duplicate delivery structurally prevented.
0
Posts that can reach a live account without human approval
3
Platform-ready crops rendered from every source item
0
Automated tests, re-run on a clean machine six weeks after handoff
What we built
Four surfaces, one authenticated data plane
Backend
Generation & publishing engine
A scheduled daily draft generator, a rendering pipeline, and a worker that wakes every five minutes to claim due posts and walk them through the provider's delivery lifecycle.
Frontend
Review dashboard
The human gate. Framing, caption, hashtags, rights check and schedule on a single screen, so approving a post is a short, unambiguous task.
Local tools
Command line & agent access
The same workflow from a terminal, or from an AI agent through a local MCP server. Credentials live in the operating system keychain — never a config file, never an environment variable.
Operations
Runbooks & release gates
Ten documented failure and recovery procedures, a deployment boundary enforced by a script, secret scanning across full version history, and three separately pinned dependency sets.
The dashboard, the command line tool and the agent interface are all clients of the same API. None of them reaches the database, the file store or the provider directly — which means every permission check happens in exactly one place.
The unlock
It doesn't care what the data is
The client's catalogue ranks itself. Every day there is a list of whatever their users interacted with most, and Dispatch turns the top eligible entry into a finished post.
Partway through the build it became obvious that nothing in that pipeline is specific to what the items are. The selection rules, the licence checks, the rendering, the approval gate and the delivery are all indifferent to the subject matter. They only need a ranked list and a source record.
Point it at a different signal — top purchased, most viewed, most saved, highest rated, most returned — and the same machine runs. Adapting it to a new source is a connector, not a rebuild.
We set out to build one client a daily post. What we actually built was a pattern for any business whose own data already knows what's working.
Same machine, different signal
- MarketplaceMost interacted-with listing today
- Retail / e-commerceTop purchased item this week
- Media / catalogueMost viewed or most saved
- Marketplace supply sideNewest listing from a top-rated seller
Each row is the same daily run with a different query behind it. The review, rights and delivery stages are unchanged.
How it works
Seven steps, and a full stop in the middle
A deterministic identity for the day
The scheduler resolves the local date and mints a post ID from it. Running twice produces the same ID, which is what makes everything after this point safe to retry.
A candidate chosen under licence rules
The system reads the client’s curated catalogue — read-only, never written — then applies eligibility, licence class, a cooldown ledger so the same item cannot resurface, and a stable ranking. The public source listing is re-verified before it is used.
The day claimed in one transaction
A single database transaction claims the post, the daily run record and the usage record together. A retry after a crash resumes the same logical post instead of starting a second one.
Copy written, credit locked
Platform-specific bodies and alt text are generated, along with the creator, source, licence and modification notice — stored as an immutable block that ordinary copy edits cannot overwrite.
Assets rendered — then the automation stops
Three versioned crops are built into a private storage prefix. Hashes and metadata are recorded; storage paths are never exposed to a client. Nothing further happens until a person opens the dashboard.
A person reviews and approves
An editor checks framing, tunes the caption, confirms the licence and marks the post ready. Licences that need judgement escalate further: only a named administrator can clear those, and they must record an explicit basis.
Publishing under lease
A worker wakes every five minutes, transactionally claims a due post, takes a lease, reads the current token version from the secret store, checks quota and creates the delivery. The image is fetched through a short-lived link signed against that one job and that exact content.
Inside the product
What the reviewer actually sees
Captured from the delivered build running in its offline review mode — a state that loads fixture content and is structurally unable to reach the provider or the live API. Names and branding shown are placeholder fixture data.

Three columns, one job each. The day’s queue with draft, ready and live counts; the framing canvas with a safe-area overlay; and the review rail. The rights check sits at the top of that rail, flagged and tinted — the first thing in the reviewer’s eyeline rather than a checkbox at the bottom of a form.

Switching crop re-frames the asset without touching the copy. Below the hashtags sits the locked credit block: the work, the creator, their handle, and a plain statement that the image was cropped and resized — the modification disclosure the licence requires. The caption editor writes around it.

A still-derived short reel treatment. The format choice has to be saved before the media is created, so a reviewer cannot approve one format and ship another. The header switches to “unsaved changes” until they do.

Swapping the day’s pick rebuilds images, captions and motion assets, and clears both the rights review and the Ready approval. The dialog names all four consequences, then answers the question the reviewer is actually asking: this does not publish anything. The safe choice holds focus.

Automatic publishing is a per-post switch, not a global setting. In the offline review mode shown here the panel keeps its full layout but marks itself simulated and states that no provider request can leave — the demo boundary is visible rather than silently inert.

Three columns become one flow with a pinned action bar. Approving a post from a phone is a real workflow for a small team, so it was a delivery requirement rather than a courtesy breakpoint.

The command-line tool authenticates in the browser, then receives its credential through a one-time loopback callback — in the request body, never the address bar, never a log. This is the rejection state: a request that cannot be verified explains itself in plain language and refuses. The exchange succeeds only once the credential is durably stored and reads back byte for byte.

The unauthenticated state. No queue, no content, no counts — the shell renders and stops. The drawing at right is the product’s thesis in one image: a source item on the left, a finished post package on the right, and the format spec in between.
Where the safety lives
Attribution is enforced, not remembered
Most content automation treats credit as a caption convention — a template someone can edit out at eleven at night. Dispatch treats it as a data constraint.
The creator, source, licence class and modification notice are generated once at draft time and stored as a locked block. The copy editor writes around it. There is no path through the dashboard, the command line or the agent interface that removes it.
Licences are then classed by how much judgement they need. A permissive licence clears with an editor's checkbox. A licence that needs review cannot be approved by an editor at all, cannot be approved by a shared access key under any circumstances, and requires a named administrator to record an explicit basis for the decision.
Every edit is revision-checked, so two people working on the same post cannot silently overwrite each other — the second write is rejected rather than merged.
Authorisation, as delivered
| Capability | Viewer | Editor | Admin |
|---|---|---|---|
| Read posts, download assets | Yes | Yes | Yes |
| Edit a draft | No | Yes | Yes |
| Regenerate copy or assets | No | Yes | Yes |
| Schedule a publish | No | Yes | Yes |
| Approve review-class rights | No | No | Named only |
| Manage the publishing connection | No | No | Named only |
Every row is enforced again on the server. Hiding a control in the interface is treated as presentation, not security.
Verification
We re-ran the delivery six weeks later
Not a claim carried over from the handoff. The repository was cloned to a clean machine, its pinned environments rebuilt from the committed lock files, and every suite executed.
318
Tests executed across four independent suites
317
Passing. One environment-specific failure, described below
4
Standing integrity checks passed: deployment boundary, secret scan, tooling manifest, dependency lock hashes
0
Console errors when the dashboard booted at desktop and phone width
The one failure
A test asserting that a demo media fixture re-encodes to identical bytes failed on the audit machine, because its video encoder is a different build from the one the fixture was pinned against. That is the test doing its job: it guards fixture reproducibility across environments and correctly reported an environment mismatch. No product code path is implicated.
We report it rather than omit it, because a case study that claims a perfect number is a case study nobody checked.
The takeaway
Automation people are willing to leave switched on
The interesting result is not that a daily post can be generated. It is that the client can leave a system running against a live account, holding a real token, on a five-minute loop — and not think about it.
That comes from a handful of decisions made early and held: a deterministic daily identity so a retry cannot double-post; transactions and leases so two workers cannot race; attribution as a locked field rather than a caption convention; a deployment boundary a script enforces; and a human gate the automation is structurally unable to route around.
The work that genuinely needed a person — is this creator credited correctly? — is now the thing the person is asked.