Back end — v0.1 Netlify · runs without the laptop

Two halves, one of them always awake

Architecture v0.1 put the whole machine in a folder on your laptop. You now want it running when the laptop is shut, so the folder splits along the only line that matters: Netlify holds the state, the clock, the API, the approval screen and the published sites. A scheduled worker does the part that needs a browser. Nothing else moves.

The load-bearing constraint: a Netlify function is stateless, has ten seconds, and has no browser, no LinkedIn session and no filesystem. So the agent cannot live there — and pretending otherwise is how this design would fail. Netlify becomes the system of record and the shop window; the agent runs on scheduled compute that has a real machine underneath it.

What sits where

Netlify — always on

Netlify DB (Neon Postgres) is the store — the six tables from v0.1, plus a job queue. Blobs hold the generated bundles and the raw research. Functions are the API: /api/leads, /api/work, /api/publish, /api/approve, /api/stats.

Scheduled functions are the clock: nightly pool top-up, hourly reply poll. They never do work — they enqueue it and wake the worker.

The worker — 25 minutes a night

A GitHub Actions job on a cron: Node, a filesystem, a browser, a logged-in LinkedIn cookie, and Claude Code headless. It claims a batch from /api/work, runs the matching skill from prompts/skills/, posts the artefacts back, and reports each job done or failed.

Free: 2,000 Actions minutes a month against roughly 800 used. Your Claude subscription authorises it through CLAUDE_CODE_OAUTH_TOKEN, so no per-token API bill.

Your laptop stays in the loop for exactly two things, both by choice rather than necessity: fifteen minutes of ad creative in Claude Design, and eight minutes firing the LinkedIn invite queue by hand.

The whole thing, as files

/ the project root IS the Netlify site ├── netlify.toml publish "." · rewrites / → Machine.dc.html ├── Machine.dc.html the hub: every prompt, every output, live KPIs ├── prompts/ the instruction set, served as readable text ├── *.dc.html the outputs — Autopilot is the sample ├── db/schema.sql six tables + job queue ├── lib/ db client, shared-secret auth ├── netlify/ │ ├── functions/ leads · work · publish · approve · stats │ │ tick (nightly) · poll-replies (hourly) │ └── edge-functions/ serves acme.danilovicioso.com out of Blobs ├── worker/drain.mjs claims jobs, runs Claude Code, posts back ├── review/ the approval screen └── .github/workflows/ the worker's cron

One repository, one deploy, no build step. The pages you already have are the site's content, so the machine and its portfolio are the same artefact — which is the point of the hub: a prospect can read every prompt that produced their packet.

The queue is the whole design

Every stage of v0.1 becomes a row in job. Functions only ever write rows; the worker only ever drains them. That single indirection is what buys the properties you asked for: the schedule survives a failed run, a crashed job retries instead of losing a prospect, and the approve gate is just a row transition.

JobWhat runsWhere
prospectYour existing weekly research scan — tops the pool back to 100, POSTs to /api/leadsWorker
researchPosts, threads, commonalities, Meta Ad Library evidenceWorker
find_emailThe five-step free cascade, stopping at the first hitWorker
generateFills the site and deck templates; writes the ad briefWorker
sendGmail send, thread id recorded against the hookWorker
Pool top-up, stale-row retirement, reply matching, KPIsNetlify

A reply stops everything for that prospect. The hourly poll matches the thread, writes an event, sets the opportunity to replied and marks its queued jobs done. The machine opens conversations; it never holds one — and that rule now lives in the database rather than in your memory.

Publishing — your deploy skill, then past it

Your deploy-subdomain playbook already works: zip, drop on Netlify, add the domain, write one CNAME in Squarespace. Keep it — it is how this site goes up today, and how the first few prospect sites go up. But it has a hard ceiling: drag-and-drop ships static files only, so the functions and the schedule never run, and every new subdomain costs a human DNS write.

RouteAutomatableWhat it costs you
Zip → drag-and-drop (your skill)No — no functions, no cronA browser session per site, one DNS record each
netlify deploy --prod from the folderYes, and it ships functionsNothing. This is the drag-and-drop feel with the back end intact
Git-connected repoYes — every push redeploysThe repo, which the worker needs anyway
One site per prospect via the Netlify APIDeploy yes, DNS noA Squarespace CNAME per prospect — the automation breaks here
Wildcard on one site, served from BlobsFullyMoving danilovicioso.com's nameservers to Netlify DNS, once

The recommendation, and its one real cost. A wildcard certificate for *.danilovicioso.com requires Netlify to run the DNS. Do that once and publishing becomes a database write forever: the edge function reads acme.danilovicioso.com, pulls the acme/ bundle out of Blobs, and serves it — no per-prospect site, no deploy, no DNS, no propagation wait. Unapproved slugs simply do not resolve.

The cost is that your MX records and the Google Workspace setup have to be recreated on Netlify DNS during the switch. That is the one genuinely risky step in this whole design, it touches your real email, and it should be done deliberately in a sitting — not by a machine on a schedule. Until then, run the API route and accept one manual DNS record per prospect.

Either way the discipline from v0.1 holds: every prospect page carries noindex, uses only public material, and never claims to be the company's own site. On the wildcard route that header is set in the edge function, so it cannot be forgotten per page.

Free-tier ceilings, and which one you hit first

ResourceFree allowanceAt 150 prospects a round
Netlify DB (Neon)0.5 GBRows are tiny; the research JSON is the only risk — keep raw scrapes in Blobs, not columns
Netlify BlobsGenerous, per-site~2 MB a prospect. Prune bundles for dead opportunities quarterly
Function invocations125k / month~4k. Never the constraint
Scheduled functionsIncludedTwo crons. Note the ten-second cap — they enqueue, never work
Edge function invocations1M / monthOnly prospect page views. Cache 60s and forget it
Bandwidth100 GB / monthThe one to watch. Image-heavy prospect sites at scale — compress before publish
GitHub Actions2,000 min / month~800 at 25 min a night. Headroom for one retry a day, not five

Nothing here bills by usage, so the failure mode of every ceiling is a stopped machine rather than a surprise invoice. That is the correct trade for this system.

Standing it up

#StepTimeNeeds you?
1netlify init in the project root, then netlify deploy --prod10 minYes — login
2netlify db init, then run db/schema.sql against it10 minYes
3Set OM_API_KEY and OM_REVIEW_PASSWORD in Netlify env5 minYes
4Push the repo; add the four worker secrets in GitHub15 minYes
5Point work.danilovicioso.com at the site — your deploy skill, step 410 minYes — DNS
6POST one lead to /api/leads by hand and watch it appear on the review screen5 minYes — once
7Everything after thatNo

An hour, once. Every step is a login or a paste; none of it is code you have to keep. Step 5 is the only one that touches the live domain, and your playbook already covers exactly what it asks for — including the Google re-verification popup that needs your own click.

What breaks first, honestly

Most likely
LinkedIn scraping
A cookie in a GitHub secret expires, or the markup shifts. The queue keeps filling and research stalls — visible on the hub as opportunities live climbing while prospects packaged flatlines.
Second
Guessed email addresses
Bounces land on a real aged inbox whose reputation the whole channel rests on. Two or three pattern guesses a day, dropped after one bounce — enforced in the schema, not by discipline.
Quiet failure
A skill that drifts
Nothing errors; the output just gets worse. The reply-rate-by-hook number is the only detector, which is why it is the KPI on the hub rather than a count of sends.
Not a risk
Netlify itself
Static pages, a Postgres row store and two crons. If the worker never runs again, everything already published stays up and the approval screen still works.
Back end — v0.1 Internal working document