How to let an AI write your blog without letting it publish

The best material for a developer blog is already in your terminal — but letting an AI write it up means guaranteeing it can't publish on its own. Here's the draft-only pattern we landed on.

AIEngineering#AI coding agents#content pipelines#API design#Claude Code#automation

If you write a developer blog about AI coding, your best material is already in the terminal: the bug you just chased down, the workaround you found, the trade-off you talked through with the agent. The problem is that writing it up is one more chore at the end of a session, so it never happens and the insight evaporates.

We wanted that write-up to cost a single command. But automating it surfaced a sharper problem — and it's the transferable one: the moment you let an AI write your posts, you need a guarantee, not a hope, that it can't publish anything on its own. What follows is the pattern we landed on for letting an AI author content into a live publication safely. It's worth copying even if you never touch Claude Code, because the shape applies to any AI-generated content flowing into something with your name on it.

What you actually get

At the end of any session, in any project, we type /blogmaker. A minute later a fully-drafted post is waiting in our admin, written from what we actually did that session — and there is no path by which the tool could have published it or emailed our list. We review, edit, and ship by hand.

That's the whole pitch: capture costs one command; releasing always costs a human. Three stages, a person between each:

  1. /blogmaker — a global command, usable in any project, that reviews the session and writes a post.
  2. A draft-only ingest endpoint that accepts the post into production — but only ever as a draft.
  3. A dev-only admin where we read it, edit it, and publish. Publishing is also the only thing that queues the newsletter — and even that waits as a draft until we send it.

The one idea to steal: constrain the endpoint, not the prompt

It's tempting to put "never publish anything" in the AI's instructions and move on. That's a wish, not a control — the next model, or a confused run, can ignore it. So we pushed the guarantee down into the endpoint, where it's structural:

// status is forced to 'draft' — this endpoint never publishes or emails.
const postData = {
  title, slug, content,
  status: 'draft' as const,
  published_at: null,
}

The caller doesn't get to set status. There is no code path from this endpoint to a published post or an email. If the token leaked tomorrow, the worst anyone could do is clutter our drafts folder. For an endpoint a CLI tool hits from arbitrary machines, that's the right blast radius — and it's why we could let the agent write completely unsupervised without losing sleep.

Match the security to that blast radius

Because the damage ceiling is "a junk draft", the auth can be proportionate: a bearer token, compared in constant time. One subtlety worth knowing if you copy this:

const a = Buffer.from(provided), b = Buffer.from(expected)
if (a.length !== b.length) return false      // required, not an optimisation
return timingSafeEqual(a, b)

timingSafeEqual throws if the buffers differ in length, so that length check isn't a shortcut — leave it out and a wrong-length token crashes the route instead of being rejected. We also fail closed with a 503 when the server token isn't configured (distinct from a bad token), and return 409 on a slug clash so the caller retries instead of silently overwriting.

Save before you touch the network

A post generated from a session is unusually expensive to lose — the context that produced it disappears when the session ends. So the publish step writes the file to disk before it tries to upload:

writeFileSync(mdPath, header + content + '\n')   // 1) local copy, always
// 2) THEN POST to the ingest endpoint

If the token's missing, the network flakes, or the slug clashes, the words are already saved and every error points at the file. It's a one-line reordering that turns "lost the post" into "paste it manually". Worth doing any time a payload is cheap to store and expensive to regenerate.

Drafts all the way down

The same move repeats at every layer: the endpoint forces a draft, publishing only drafts the newsletter, and sending it is a manual click after a final read in a real inbox. Each automation hands off to a person instead of chaining into the next one. With an AI in the author's seat, that's the property that matters — its output is always a proposal. It costs us two clicks per post and buys the confidence to let it write on its own.

What to take away

  • Constrain the endpoint, not the prompt. "Please don't publish" is vibes; an endpoint that can't publish is a guarantee.
  • Prototype the editorial voice with a copy-paste prompt before building anything — you'll tune the writing ten times and the plumbing once.
  • Write to disk before the network call when the payload is expensive to recreate.
  • Size the auth to the blast radius — a draft-only endpoint needs a token and a careful compare, not OAuth.

The benefit isn't really "AI writes our blog". It's that capturing what we learn no longer depends on anyone finding the willpower at 6pm — while a human still decides what the world sees.

Enjoyed this? Get Hurricane Signal

Field notes on building real things with LLMs — occasional, practical, no hype.