~/progetti/magosox/README.md

COOKIES_ADVICE PRIVACY_POLICY.

~/progetti/magosox/README.md

magosox

{ date: “2026-08-01”, status: “live”, stack_modules: 12 }

Automates a brand's social content — it picks the topic, writes the copy, produces the graphics and publishes

magosox produces a brand's social media content and puts it online on its own. At the times it's been given it decides what to talk about, writes the copy, turns it into graphics and publishes: nobody has to open anything. The hard part isn't the publishing — the hard part is keeping a recognisable voice while doing it. An automated post that sounds like an automated post is worth nothing, and that's exactly where this project puts its effort.

## The brand context is data, not code

What makes a post recognisable — the voice, the way it addresses the reader, the things the brand knows and the things it deliberately doesn't say, along with the stock of topics — lives inside the product in magosox, not in the source code. It's written and corrected from the interface, and a change applies from the next post onwards, with no release. Everything else follows from that: taking on a new brand is configuration rather than development, and each brand carries its own context without touching the others.

## Structure the prose, don't shred it

The quality of a post doesn't come from an architecture. It comes from about a thousand well-written words. That distinction sounds obvious, and almost every system of this kind misses it: it shreds those words into columns — tone, formality, useEmoji: false — and reassembles them, producing worse instructions than the ones it started from. In magosox the voice rules are rows holding text rendered verbatim, line breaks and indentation included. The system only knows when to inject them, in what order, and under which heading: it structures the scope, the weight and the placement of the prose, never the prose itself. So that work on the engine can't quietly change the voice, a regression test recompiles the prompt from those rows and compares it, character by character, against a frozen reference.

## A ledger instead of a query

To keep the feed from repeating itself, the system has to know what was talked about recently. The obvious move is to derive that from the published posts, and it doesn't hold. The window counts posts, not days, and more than one post can go out on the same day: no chronological sort can put them in order. Then there are the one-off posts written on request, which reference no topic in the catalogue and so could never be foreign keys. So anti-repetition is an append-only ledger with a sequence number of its own. A scheduled post burns its topic immediately and hands it back if publishing fails — a lifecycle a post's status couldn't represent without lying.

## Limits announced, limits enforced

Every text surface has two different numbers: the one announced to the model and the one the check actually enforces. A title might be announced at 55 characters and rejected only at 65, while the correction message still says 55. That margin of roughly 18% is what keeps the retry rate near zero, and it weighs more than it sounds: the second attempt produces, on average, worse copy than the first. Collapsing the two into a single column means either flooding the correction loop or letting every title grow by 18%.

## From idea to published post

The pipeline runs unattended, at the times the calendar sets, and each step leaves a trail you can read back:

·Topic selection: cadence of the content pillars, cooldown, least-recently-used rotation across categories, seasonality — with a seeded random generator, so a choice can be replayed
·Generation: prompt compiled from the brand context, structured output, sanitising, validation and a conversational correction loop
·Rendering: Mustache plus headless Chrome produce the JPEGs; theme, template and Chrome version are pinned on every render
·Publishing: Instagram Graph API in three phases, resuming from containers already built, with automatic token renewal
·Scheduling: slots that fix days and time — and, if wanted, the pillar or the format for that day — occurrences materialised from them, and a worker that generates ahead of time and publishes on the exact hour

## The interface

The admin is where the brand context actually gets edited, without anyone going near a repository. Sign-in is passwordless, by email code; there's no registration, accounts are seeded from the terminal.

·Context: prompt sections, voice rules and documents, all editable
·Prompt: the exact text that would go to the model, without calling it
·Topics: the stock, with eligibility and distance from last use in a column
·Taxonomy: pillars with their cadence, categories, tags with their colour, formats with their announced and enforced limits
·Posts and calendar: what goes out when, and in what state
·Settings: providers for model, storage, email and rendering, each with the result of a real check

## Tech stack

The approach to configuration is worth a note: environment variables are the only way to configure the service before the database exists, so they stay. From the first boot onwards, though, the stored value wins, and the interface shows which of the two is winning. Changing a model key at three in the morning shouldn't require server access.

·API: NestJS on Fastify, Prisma and PostgreSQL
·Admin: React, Vite, HeroUI, TanStack Query and Router
·Generation: Claude with structured output
·Rendering: headless Chrome, with fonts inlined as data URIs because Chrome applies CORS to @font-face even between file:// documents
·Storage: Cloudflare R2 — Instagram downloads the images from a public URL
·Sign-in: better-auth with an email one-time code, session in an httpOnly cookie
·Operations: Docker Compose behind a shared Caddy, with automatic TLS

magosox has been in production since 1 August 2026, publishing unattended on the cadence it's been given. Before it went live the history of content already published was loaded in as well, so that anti-repetition had a memory from day one instead of having to build one up over weeks.

// stack
[nestjs”, fastify”, prisma”, postgres”, react”, vite”, typescript”, claude-api”, instagram-api”, docker”, caddy”, content-automation”, ]