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.
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.
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.
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.
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.
| Job | What runs | Where |
|---|---|---|
prospect | Your existing weekly research scan — tops the pool back to 100, POSTs to /api/leads | Worker |
research | Posts, threads, commonalities, Meta Ad Library evidence | Worker |
find_email | The five-step free cascade, stopping at the first hit | Worker |
generate | Fills the site and deck templates; writes the ad brief | Worker |
send | Gmail send, thread id recorded against the hook | Worker |
| — | Pool top-up, stale-row retirement, reply matching, KPIs | Netlify |
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.
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.
| Route | Automatable | What it costs you |
|---|---|---|
| Zip → drag-and-drop (your skill) | No — no functions, no cron | A browser session per site, one DNS record each |
netlify deploy --prod from the folder | Yes, and it ships functions | Nothing. This is the drag-and-drop feel with the back end intact |
| Git-connected repo | Yes — every push redeploys | The repo, which the worker needs anyway |
| One site per prospect via the Netlify API | Deploy yes, DNS no | A Squarespace CNAME per prospect — the automation breaks here |
| Wildcard on one site, served from Blobs | Fully | Moving 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.
| Resource | Free allowance | At 150 prospects a round |
|---|---|---|
| Netlify DB (Neon) | 0.5 GB | Rows are tiny; the research JSON is the only risk — keep raw scrapes in Blobs, not columns |
| Netlify Blobs | Generous, per-site | ~2 MB a prospect. Prune bundles for dead opportunities quarterly |
| Function invocations | 125k / month | ~4k. Never the constraint |
| Scheduled functions | Included | Two crons. Note the ten-second cap — they enqueue, never work |
| Edge function invocations | 1M / month | Only prospect page views. Cache 60s and forget it |
| Bandwidth | 100 GB / month | The one to watch. Image-heavy prospect sites at scale — compress before publish |
| GitHub Actions | 2,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.
| # | Step | Time | Needs you? |
|---|---|---|---|
| 1 | netlify init in the project root, then netlify deploy --prod | 10 min | Yes — login |
| 2 | netlify db init, then run db/schema.sql against it | 10 min | Yes |
| 3 | Set OM_API_KEY and OM_REVIEW_PASSWORD in Netlify env | 5 min | Yes |
| 4 | Push the repo; add the four worker secrets in GitHub | 15 min | Yes |
| 5 | Point work.danilovicioso.com at the site — your deploy skill, step 4 | 10 min | Yes — DNS |
| 6 | POST one lead to /api/leads by hand and watch it appear on the review screen | 5 min | Yes — once |
| 7 | Everything after that | — | No |
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.