lukewilkinson.io
← Projects

product

Atomatize

Turns one long-form piece into a week of posts in the author's own voice, then checks every draft for the patterns that make writing read as machine-made and repairs the ones that do.

Visit atomatize.com ↗

What it does

Paste a blog post, article, newsletter, PDF, or transcript, or give it a link, and Atomatize returns twelve drafts: four LinkedIn posts, five X posts, a six-tweet thread, a newsletter section with a subject line, and a 600 to 800 word article.

Each draft is written in a voice profile built from the author’s own samples. The samples are analysed once for tone and measured for style rates, then discarded. Only the description and the numbers are kept.

The checker

Generated drafts tend to share a set of tells. Atomatize runs every draft through rule groups that catch them, some ported from the open-source slopvac and stop-slop projects. Out of the box those rules flagged 97 to 100 percent of human-written text, which made them useless as a quality gate. Two changes fixed that:

  • Voice-aware rules. Any rule that the author’s own samples trigger at least 0.5 times per thousand words is switched off for that author. If you write that way, it isn’t a tell.
  • A length-scaled threshold. A draft is flagged only when its hits reach max(1, K × words / 180), with K set by a Relaxed, Balanced, or Strict setting.

On Balanced, the tuned checker flags 16 percent of AI drafts and 2 percent of human text. Hard requirements, such as a tweet’s 280 characters, a newsletter subject, or an article title, are always enforced.

A flagged draft gets one repair pass: a model call asked for the smallest change that clears the findings, with new facts forbidden. The rewrite is kept only if it has fewer findings than the original. Users can also trigger a repair themselves, and their own edits are re-checked but never rewritten.

Choosing the model

The primary model was chosen by a blind bake-off across four providers on the same source pieces, scored for writing quality against cost per generation. Every draft records which model wrote it, and the “another version” button produces a second draft from a different model. Each time a user picks one of the pair, that is a labelled preference.

Stack

  • Next.js 16 with TypeScript, Tailwind, and shadcn/ui.
  • Postgres through Drizzle ORM, with row-level security isolating each brand under a dedicated non-superuser role.
  • No separate queue. Generation runs in-process after the response, with progress written to Postgres so an interrupted run can be retried. Social and long-form drafts run as separate batches, so a long-form failure doesn’t cost the social drafts.
  • An MCP server lets agents use it directly, authenticated with scoped API keys or OAuth 2.1 (read, write, and publish scopes per brand).
  • better-auth handles sign-in, including passkeys, and Stripe handles billing.

Infrastructure

  • Hosting: Azure Container Apps, scaling from zero to two replicas, with a container registry, Key Vault, a managed identity, and Log Analytics. A daily Container Apps job publishes scheduled posts.
  • IaC: everything is in Terraform, run remotely with OIDC credentials. Secrets are Key Vault references by name, so their values never appear in Terraform code or state.
  • CI/CD: every pull request runs lint, type checks, and tests against a real Postgres 17, plus a second suite that runs as the non-superuser app role to catch row-level-security mistakes. A merge to main builds and pushes the image, updates the app and the publish job, and polls the new revision’s own health endpoint until it serves all traffic, so a bad revision can’t pass by answering from the old one.
  • Observability: Sentry, an Azure dashboard defined in Terraform, and a Grafana dashboard over count-only reporting views.