Twenty One Media
automationJune 6, 2026

Our Ops Audit Is a URL That Becomes a PDF

The free AI operations audit we offer works like this: we research a business publicly, find the manual workflows burning the most hours, estimate the cost in dollars per year, and write up what automation would look like. Then we send the prospect a PDF.

For a while, the PDF was a manual step. Write the audit in a doc, drag it into a template, export. It worked, but it was a friction point we'd have to scale through eventually.

We replaced it with a pipeline. Now the deliverable comes from code.

The Structure

Each audit lives as a JSON file in content/audits/. The file defines the business name, date, a snapshot paragraph, and an array of workflows. Each workflow has:

  • What we observed (sourced from their website, job posts, or public reviews)
  • Hours per week as a range
  • Dollars per year as a range
  • The assumption behind those numbers, written explicitly
  • What the automated version does

The schema enforces that every audit follows the same shape. We can't ship an audit that's missing the hours estimate or the automation description because the TypeScript type won't compile if it is.

The Page

A Next.js route at /audit/[slug] renders the audit as a clean, white-background page. No site nav in the way. No sidebar. Just the content: snapshot, workflow cards with the hour and dollar ranges, a total opportunity block, and a three-step path section that names what comes next after the free audit.

The route is noindex. It's not meant to rank. The slug itself contains a random suffix so it's not trivially guessable. We send the URL to the prospect; if they share it, that's fine, but it's not a public directory.

The page is also designed to print. Cards use break-inside-avoid. Margins are set. The CTA at the bottom includes a direct link back to our contact page.

The PDF

A single Playwright script handles the conversion:

node scripts/render-audit-pdf.mjs sample-business-x0x0

It opens Chromium, navigates to the audit route, injects a small CSS block that strips the site nav header and footer (but preserves the audit's own header and footer inside <main>), and calls page.pdf(). The output lands in audits-pdf/.

The CSS strip was the one fiddly part. The site layout wraps everything in a <main> with a fixed nav <header> above it. The audit page renders its own <header> inside <main>. A naive header { display: none } would kill both. The selector header:not(main header) targets only the site nav, leaving the audit heading visible in the PDF. Same logic for the footer.

Why This Works for Us

The JSON format forces discipline on the audit itself. Before we had the schema, a draft could be vague: "hours saved: significant." The type definition requires [number, number]. You have to commit to a range and write the assumption that backs it up. That discipline makes the deliverable better, not just more automatable.

The page-to-PDF path means the design is maintained in one place. If we update the layout or typography on the audit page, every future PDF gets the update. There's no separate InDesign template to keep in sync.

And the URL is useful on its own. Prospects can open it on their phone before the call. It links directly to our contact form. If someone forwards it, the page explains the path forward without us in the room.

The whole pipeline from new audit to shareable PDF is: write a JSON file, run one command, attach the file to an email. That's it.