The plumbing behind a one-person publishing shop
Email-reply publishing, automatic rollback, a Vercel token that failed this morning, and the two rules that keep three small sites from breaking each other.

GDM Digital Media Group is the name on the footer of everything I ship. Behind it there's one person, some cron jobs, and a pile of small decisions about how things get published when I'm not at the keyboard. This post is about those decisions, including the one that failed this morning.
The properties, for context: FixMyPrompt, a prompt grader with a blog and a template library. bestAIpacks, an AI tools directory with three people editing it. This site. And a Kindle book pipeline that deserves its own post.
Publishing by replying to an email
FixMyPrompt's blog gets a new draft written twice a week by a script. The script doesn't publish anything. It writes a markdown file to a drafts folder and emails me the title, the description, and a link to the full text.
If I reply with the word PUBLISH, an inbox watcher runs the publisher. The publisher moves the draft into the blog folder, builds the site, deploys it, commits the file, and pings IndexNow with the new URL. If any step fails it moves the file back and emails me the error with a note to reply again once I've fixed the cause.
I like this better than any CMS I've used. Approving is one word in a reply, and nothing goes live because a timer fired.
What failed this morning
The email said:
publish failed, rolled back: Command failed: vercel --prod --yes
Error: The specified token is not valid.
The Vercel CLI keeps its login in a data directory. On this machine there are two of them, because VS Code runs as a snap and sets its own data path, while cron uses the plain one under my home directory. Whichever copy the CLI used last gets the refreshed token written back to it. The other copy goes stale. I had "fixed" this twice before with symlinks, and both times the CLI eventually rewrote a file and replaced the link with a real file.
Today's fix was a directory-level symlink, which survives the rewrite. The fix that should have been first was a proper API token in the publisher's environment, which the publisher already knew to prefer over the login session. The token took two tries because the first one was scoped to a single project, and the CLI needs an account-level token to figure out who it is. If you ever see "User not found" from vercel whoami, that's why.
The draft that failed was retried and went live within the hour. Nobody had to re-approve it, because the approval was still on record and the file was back where it started.
One deploy per change
A rule I learned the expensive way last week: deploy once. I shipped a feature, then a copy fix, then a link, as three production deploys inside two minutes. A tab I'd left open from before the first deploy tried to load a JavaScript chunk that no longer existed after the third one, and the page fell over.
Vercel sells a feature for this called skew protection. The cheaper fix was an error boundary that recognises a chunk-load error and reloads the page once, plus batching changes so they go out together. Both are in place now.
Three people, one main branch
bestAIpacks is edited by two writers through a git-backed CMS that commits straight to main. When I push code, I push to main too. If I push a burst of small commits while one of them is mid-save, their save errors out, and once it silently dropped a real edit.
So the rule for code is: work on a branch, batch the commits, rebase on main, merge with one push. The CMS commits replay cleanly on top of a single merge. They don't survive ten pushes drip-fed over an afternoon. It's written down in the repo so I stop relearning it.
Generated files, like the search index and the LLM-readable text dumps, aren't committed at all. They're rebuilt on every build from the content, so there's one source of truth and no merge churn.
Why write this down
None of this is hard engineering. It's the kind of thing that only breaks when you're not looking, and for a one-person shop that's most of the time. Every item above exists because something broke once and I didn't want to be the fix the next time.
If you run a couple of small products the same way, let the automation do the work but keep the decision to publish in a human's hands. Then make that decision cheap enough that it never becomes the bottleneck.
Comments
No comments yet. Be the first.