Skip to content
← All posts
5 min read

The architecture behind analytics you actually own

One serverless function, one small database, three kinds of event. Here is how it scales to a hundred million events for tens of dollars, and how we just locked the contact box against bots.

The architecture of analytics we fully own, and the anti-bot stack we shipped this week.

We built our own analytics, and we mean the whole thing: the code that catches an event, the database it lands in, and the dashboard we read it from. It runs in production today at metrics.gattyworks.com, measuring our own launches and a client site. This week we also shipped the security that keeps bots out of it. Here is the full design, the numbers we think it can hold, and the honest edges.

The entire system fits in one picture

There is one serverless function running at the edge, and one small SQL database. That is the system. No origin server, no cron, no queue, no message bus, no cookie, and no third party in the middle holding your data. The public internet can reach exactly one thing: a single write endpoint.

Write and read are two separate doors that never share a key.

A visitor taps something. A tiny script, about a kilobyte, posts one event to the function, which writes one row and returns. The dashboard is a separate door: an operator signs in through single sign-on, behind a secret path, and only then can read anything back. The side that writes and the side that reads never share a key, and the write path exposes no read route.

One small habit worth stealing: do not park your admin console at /admin. Ours sits behind a long random path, so a scan of the domain finds the public page and nothing else. The sign-in wall is the real lock, but there is no reason to advertise the door.

Three kinds of event are enough for any funnel

We assumed we would need a sprawling taxonomy of event types. We did not. The whole system speaks three words. A click means someone paid attention. An answer means they picked an option. A contact means they handed over an email or a phone number. Every funnel we have wanted to build is a sequence of those three, and nothing else.

Because the vocabulary is fixed and tiny, the server never changes per use case. What changes is a small piece of config that says which steps to show. An email grab is one contact step. A qualify-then-call flow is a click, two questions, then a contact. Same engine, different config.

{
  "steps": [
    { "kind": "question", "id": "building", "prompt": "What are you building?" },
    { "kind": "contact", "fields": ["email", "phone"] }
  ]
}
The nice surprise: a flow config this small is something an AI can write from a plain sentence, and a short validator can check before it ever ships.

How much can it hold

Here is the trick that makes the numbers work. We only record intent: a click, an answer, a contact. We never fire on pageviews, scrolls, or mouse moves. For the same human traffic that is ten to a hundred times fewer writes than typical analytics. So the workload is already tiny before any platform limit shows up.

On the free tier, roughly a hundred thousand of these events a day, about three million a month, costs nothing at all. That covers most real sites, launches, and waitlists outright. Past that, the paid math stays gentle, and these are back-of-the-envelope figures, not a benchmark:

Interaction-events per monthRough cost
3,000,000$0 (free tier)
10,000,000about $5
50,000,000about $17
100,000,000about $82 plus storage
Theorized cost. An interaction-event is a moment of intent, not a page load.

Still on the back of the envelope: one database on the tier we run should hold tens of millions of lifetime events. When a single tenant outgrows that, you do not rewrite anything. You give each tenant its own database and one function binds them all, so capacity grows in a straight line.

Past one database, you shard by tenant. The function stays the same.

The one box that bots care about

Clicks and answers are cheap noise. The place spam actually hurts is the contact box, where a script could mint a thousand fake leads in a loop. This week we shipped three layers to stop that, and left clicks and answers frictionless.

The contact submit is the only step that runs the gauntlet.
  • An invisible bot challenge on submit. Real visitors practically never see a checkbox; a token is minted silently and verified on the server. If verification fails, the contact is refused.
  • A per-browser cap. A best-effort fingerprint lets us accept two submissions from a browser, the first plus one edit to fix a typo, and reject the third.
  • The lock lives on the server, not the browser. The count is enforced in the database, so clearing local storage does not reset it. The browser side is only there to make the experience nice.
The rule we settled on: submit, edit once, then locked. Forgiving enough for a real person who fat-fingered their email, unforgiving enough that a script gets nowhere.

How this compares to what you already pay

Honest framing. Prices move; the structural differences are the durable part.

  • Mainstream free analytics: genuinely free, but the data lives on someone else's servers, high volume gets sampled, you carry the consent-banner overhead, and storing personal contacts is against the terms. Here you own the raw rows and nothing is sampled.
  • Privacy-first hosted analytics: pleasant and lightweight, but priced per pageview and still on the vendor's box. At ten million pageviews a month you are into the low hundreds of dollars, and it is not your database.
  • Product analytics suites: powerful and heavy, a full SDK on the client and your data on their cloud. Great for deep product questions, overkill for did-this-launch-land.
  • Self-hosted analytics: the closest in spirit, you own your data, but you are running a heavy data stack yourself. We trade breadth for something one person can hold in their head.

Where we would change our minds

The edges, stated plainly. We would reach for something bigger if a tenant needed full-text or high-cardinality analytical queries a small SQL database cannot serve, if a single tenant blew past the per-database ceiling and could not tolerate rollups, or if event delivery had to be guaranteed rather than best-effort. None of those are close for the traffic we and our clients generate, and each is an addition, not a rewrite.

The design is deliberately unexciting. Own the data, keep the system small enough to reason about, let the free tier carry most sites at no cost, and put the security only where it matters. If you want this wired into your own launch, that is something we do.

EngineeringArchitectureAnalyticsSecurityBuildInPublicIndieHackersFirstPartyAnalyticsGattyWorksEdgeComputingWebAnalyticsDataOwnershipServerlessSoftwareArchitecturePrivacy

Ready to know?

Send what you want checked or built. Fixed scope, price, and date in writing inside 24 hours, or the website or audit fee on your first project is refunded in full.

24 clock hours. Weekends included.