- Oct 11, 2026
- 6 min read
Cloudflare Today, Open Source When Needed: A Lead Engine Built to Swap
Over the last few months I built a small inbound lead engine for my sites: the forms on six properties, the website audit, the ebooks newsletter and paid bookings all end up in one place, with consent, double opt-in, retention, alerts and follow-up emails.
Most of it runs on Cloudflare: Pages for the sites, one Worker for every form, KV as the inbox. I care about open source and self-hosting, and I have a homelab. So why lean on a platform?
Because it’s the pragmatic choice today, and I built it so that leaving is a known amount of work rather than a rewrite.
What Cloudflare does here
flowchart LR
V([Visitor]) --> P["Cloudflare Pages<br/>static sites"]
P -->|"same-origin /api/forms/*"| W["leads-forms Worker"]
W --> KV[("Workers KV<br/>leads · suppression · spam")]
W -.->|alert| N["ntfy (homelab)"]
W -.->|"confirm emails"| M["Mailjet API"]
H["leads-hub (homelab)<br/>SQLite mirror · drips · digest"] -->|"pull, read-only"| KV
H -->|drips| M
A["Website audit<br/>(homelab, Docker)"] -->|"server-to-server lead"| W
- Pages serves the static sites (Astro and Hugo) and applies
_headers. - The Worker is reached on every site at the same path,
/api/forms/*, through zone routes (one forms Worker for every site). It validates, records consent, stores the lead, sends the confirmation email and fires an alert. - KV holds leads with a TTL per kind, so retention is enforced by the platform (retention as a KV TTL), plus a suppression list that stores hashes, not addresses (a do-not-send list).
- The edge: DNS, TLS certificates, caching, DDoS protection, cookieless analytics, and a Tunnel so the homelab never opens a port.
Why that’s the pragmatic choice
The homelab can be down and nothing is lost. The forms keep working when my server reboots, my internet drops or I’m travelling. The homelab pulls from KV when it’s up (a pull-only homelab). Self-hosting the capture path would make my home connection a single point of failure for every lead.
The volume fits in the free tier. A small business gets tens of leads a month, not thousands. At the time of writing, Workers’ free plan allows 100,000 requests a day and KV 1,000 writes a day. The only cost in the whole engine is email sending.
There’s very little to operate. No server to patch for the public path, TLS renewed automatically, and attacks absorbed at the edge. For one person, time spent operating infrastructure is time not spent with clients.
It’s fast to change. A deploy is wrangler deploy or a git push. A new
site joins the engine with a route and an entry in a registry file.
What keeps the exit cheap
Pragmatism turns into lock-in when every file starts to depend on the platform. These rules prevent that:
- Web standards, no platform SDK. The Worker is a plain
fetch(request, env, ctx)handler usingRequest,Response,crypto.subtleandURL. About 1,450 lines of TypeScript, no framework, nocloudflare:imports. The only platform-specific pieces are theenv.LEADSbinding andctx.waitUntil. - Same-origin paths. Sites post to
/api/forms/submiton their own domain, not to a Cloudflare URL. Swap what answers that path and no site changes. - Data already leaves the platform. The homelab mirrors every lead into SQLite, keeps backups and runs the drips. KV is the inbox, not the only copy.
- Senders behind an adapter. Email goes through a switch
(
EMAIL_PROVIDER): Mailjet in production, Mailtrap as an alternative, Mailpit or any SMTP relay in development (one sender adapter, two providers). - Rules live in code, not in a dashboard. Retention per kind, the site registry, spam blocklist matching and consent wording are versioned files, so they move with the code.
- Decisions are written down (ADRs for a one-person business), including the alternatives I didn’t pick, so the exit plan already has a starting point.
When I would swap
None of these is true today; each would change the answer:
- Volume. Past the free tier, paid plans are still cheap, but at some point a small VPS is cheaper and simpler.
- A client requirement. Some clients need data processed in a specific place, or by no US provider at all.
- Price or terms change. Free tiers can shrink.
- Needing to query. KV is a key-value store. When I need real queries across leads, SQLite or Postgres is the right tool; the homelab mirror already gives me that.
The exit plan, piece by piece
| Piece | Today | Open-source swap | What keeps it cheap | Effort |
|---|---|---|---|---|
| Static sites | Cloudflare Pages | Caddy or nginx in a container | Plain static output; _headers maps to a Caddy header block | Low |
| Forms endpoint | Worker on /api/forms/* | The same handler on Node or Bun (e.g. with Hono) behind the reverse proxy | Web-standard fetch handler, same-origin path | Medium |
| Lead inbox | Workers KV with TTLs | SQLite or Postgres plus a nightly expiry job | Already mirrored to SQLite; retention rules in one file | Low–medium |
| Secrets | wrangler secret | .env file, Docker secrets or SOPS | Read from env only | Low |
| Confirmation and drip emails | Mailjet API (not Cloudflare) | Postal or any SMTP relay | Sender adapter; a config change | Low |
| Alerts | ntfy, self-hosted | Already open source | — | None |
| Homelab exposure | Cloudflare Tunnel | WireGuard to a small VPS, frp, or Headscale | Only alerts and the audit are exposed; the hub only pulls | Medium |
| Analytics | Cloudflare Web Analytics | Umami or Plausible CE | One script tag (I ran Umami before) | Low |
| Spam handling | Honeypot + blocklist in code | Same code | Nothing platform-specific; Turnstile deferred on purpose | None |
| Daily rebuilds | Cron Worker + deploy hooks | systemd timer or CI schedule | A POST to a build hook | Low |
| Booking | Cal.com cloud | Self-hosted Cal.com (open source) | Signed webhook into my own pipeline | Medium |
| Website audit | Docker on the homelab | Already self-hosted | — | None |
| DNS, TLS, CDN, DDoS | Cloudflare | Caddy automatic TLS + any DNS host; no open-source global edge | Standard DNS records | High: the one piece I’d accept keeping |
The table’s honest conclusion: almost everything could move in days. The hard part is the edge: global caching and DDoS absorption have no open-source equivalent you can run yourself. If I left, that’s the piece I would still rent from somebody.
Takeaways
- Use the platform for what you can’t cheaply do yourself: availability while your server is off, the edge, and zero operations on the public path.
- Keep the platform out of your code: web standards, same-origin paths, adapters, rules in files.
- Keep a copy of the data off-platform from day one. It’s the backup, and it’s the first step of any exit.
- Write the exit table before you need it. If a row says “rewrite”, that’s where the lock-in is.