• Sep 6, 2026
  • 8 min read

From Five Lead Silos to One Lead Hub: Before and After

When I audited my own sites, the honest answer to “who has contacted my business?” was: I don’t know. Leads were spread across an email inbox, three SaaS dashboards, a PocketBase database, a CSV file and Cloudflare KV. Each site had grown its own way of capturing people, and none of them talked to each other.

This post compares that starting point with the setup I run now, including the part that surprised me most: the PocketBase database I removed from the loop because the app only ever wrote to it.

Before: five mechanisms for five sites

SiteHow it captured leadsWhere they ended up
multibodysystemsdynamics.comIts own Pages Function → Workers KV → phone alertKV (no site in the key, no consent record, no expiry)
www.jalcocertech.comFooter form posting to formsubmit.co; the contact form sent nothingAn email inbox (and a dead form)
consulting.jalcocertech.comA Formbricks qualifier survey, a Cal.com booking embed, a chatbot (off in production)Formbricks and Cal.com dashboards
ebooks.jalcocertech.comA link to another Formbricks surveyFormbricks
webaudit.jalcocertech.comAudit modal → Express → PocketBase leads + audits, plus a CSVPocketBase and a CSV file tracked in git
fossengineer.comNo forms at all—

Five mechanisms, two different contact addresses, three privacy policies in three different states, and analytics code for a tool I had already dropped. Most of it wasn’t wrong in isolation. Together it meant:

  • No single list. Answering a deletion request meant remembering five places.
  • No consistent consent. One site bundled marketing into a required checkbox; another recorded consent: YES for everyone regardless.
  • Retention by hope. Nothing expired anything, except where I’d set it by hand.
  • Vendors in the critical path. If a survey account lapsed, the main call-to-action on the consulting site would stop working.

After: one lead hub

6 sites ── form POST (same-origin /api/forms/*) ──► one Cloudflare Worker
webaudit backend ── server-to-server (keyed) ─────►   ├─ Workers KV: the record of truth
Cal.com ── signed webhook ────────────────────────►   ├─ phone alert (email domain only)
Mailjet ── bounce/complaint webhook ──────────────►   ├─ double opt-in emails (Mailjet)
                                                      └─ do-not-send list (hashes)
homelab (pull-only) ── every 15 min ── SQLite copy · daily backups · drips · weekly digest

Every site posts to the same Worker on its own domain (so no CORS and no third-party script), and every lead lands in one KV namespace with a key like <site>:<kind>:<time>:<id>. The details of each piece are in earlier posts: the forms Worker, double opt-in with signed links, the do-not-send list, the homelab mirror, the Cal.com webhook and server-to-server audit leads.

The PocketBase question

Webaudit was the best-engineered site of the lot: rate limits, an SSRF guard, expiring report links, and PocketBase as its lead and audit store. After wiring it to the lead hub, every audit was saved twice — once in PocketBase, once in KV.

So I checked what the app actually read from PocketBase. The answer was nothing. Reports are served from files through time-limited tokens; the frontend never queries the database; the only code that touched PocketBase created records (and, later, a retention job I’d just written to delete them). PocketBase was a write-only copy with its own login, its own retention job, and a blind spot: deleting someone from the lead hub didn’t delete them there.

I turned it off by default rather than deleting the code (PB_ENABLED=false), and the lead hub now receives the audit summary it was missing: overall score, detected platform and the category scores. If I ever build client features — logins, “my past audits”, shareable report pages — PocketBase is one setting away.

Pros and cons, honestly

Before (per-site tools + PocketBase)After (one lead hub)
One list of everyone who got in touch❌ Five places✅ One KV namespace, one SQLite copy
Right to erasure❌ Manual, easy to miss a store✅ One forget command (plus the provider’s dashboards for bookings and email)
Consent❌ Bundled in places, faked as YES in another✅ Recorded per lead with wording version and time; double opt-in for every subscription
Retention❌ Mostly none✅ Per kind, enforced by KV expiry (90 days for unbooked qualifiers, 24 months for enquiries, until unsubscribe for newsletters)
Third-party scripts on my pages❌ Survey widgets, an email relay✅ None for forms; works without JavaScript
AlertsSome sites only✅ Every lead, privacy-friendly (email domain only)
Time to set up a new form✅ Minutes in a SaaS dashboard❌ Code: a registry entry, a form, a deploy
Survey analytics (drop-off per question)✅ Formbricks had it❌ Counts in a weekly digest, nothing per question
A ready app database for future features✅ PocketBase⚠️ Switched off, one setting to bring back
Who maintains compliance featuresThe vendors❌ Me: unsubscribe, bounces, suppression, retention are my code and my tests
Blast radiusA bug stays on one site❌ One Worker serves six sites — tests and a careful deploy matter
Platform dependencySpread across vendors⚠️ Concentrated on Cloudflare (Workers, KV) plus a homelab

The trade I made is clear in that table: I gave up convenience (dashboards, no-code edits, survey analytics) for control (one list, real consent, real retention, no vendor scripts). For a one-person business that wants to be able to answer “what do you hold about me?” in one command, that’s the right side to be on. For a team that edits surveys weekly, it might not be.

What I capture now

The complete picture after the migration — every form, what it stores, what happens with consent, how long it’s kept:

SiteForm or sourceKindWhat is storedEmail follow-upKept for
multibodysystemsdynamics.comContact form; ebook requestenquiry, ebookName, email, project, message (ebook: email); page, country, campaign tagsReply only24 months
fossengineer.comQuestion formenquiryEmail, question, optional name; page, countryReply only24 months
fossengineer.comQuarterly-email boxnewsletterEmail, consent wording + timeDouble opt-in, then one email per quarterUntil unsubscribe (+30 days)
www.jalcocertech.comFooter / contact form, optional “updates” tickenquiryEmail, message, optional name; marketing consent if tickedReply; updates only after double opt-in24 months
www.jalcocertech.comMid-article newsletter boxnewsletterEmail, consent wording + timeDouble opt-in, then a 3-email welcome seriesUntil unsubscribe (+30 days)
consulting.jalcocertech.comQualifier formqualifierOffer, question, stage, budget, timeline, optional name/role, emailReply; updates only after double opt-in90 days if no booking
consulting.jalcocertech.com”Notify me” boxnewsletterEmail, consentDouble opt-inUntil unsubscribe (+30 days)
consulting.jalcocertech.comCal.com webhookbookingName, email, time, event, paid or not — no notes”PAID BOOKING” phone alert24 months
ebooks.jalcocertech.comFooter “new ebooks” boxnewsletterEmail, consentDouble opt-inUntil unsubscribe (+30 days)
webaudit.jalcocertech.comAudit modal, sent by the backendauditName, email, audited site, score, platform, category scores; optional tips consentTips only after double opt-in24 months
any siteBlocklisted senderspam:*The submission, for reviewNone, no alert30 days

Across all of them: sign-ups never confirmed are deleted after 30 days; an unsubscribe leaves only a SHA-256 hash so the address is never emailed again; bounces and spam complaints from the email provider feed that same list; and every alert on my phone shows the email domain, never the person.

Takeaways

  • Audit your own capture points before building anything; the problems were in the gaps between tools, not inside any one of them.
  • Before keeping a second database “just in case”, check whether anything reads it. A write-only store is a liability with a login.
  • Turn things off behind a setting instead of deleting them; the cost of keeping the option is one line.
  • The final table is the real deliverable: if you can’t write it for your own business, you don’t know what you hold.