Blog · Building Hellofew, week by week, part 1 of 8
Week of 24 August 2026: the schema before the product
Adarsh Mamgain · 31 October 2026
On 28 August 2026 the repository had one commit, an architecture document, and by the end of the day it had eleven. Not one of them did anything a customer would notice. There was no product page, no LinkedIn, no message. There was a Postgres schema, a login, a worker that said it was alive, and a Dockerfile. I want to start the series there because it is the least interesting week and the one I would do the same way again.
The product I was setting out to build runs other people's LinkedIn accounts. That sentence should scare anyone writing the first line of code for it. A bug in a to-do app loses a to-do. A bug in this loses somebody the account they spent ten years building, or shows one customer another customer's replies. So the first day was spent on the two things that make those two failures hard rather than easy, and Hellofew did not exist as a name for another six weeks.
Counted from the git log on 2026-10-29. The two empty weeks are real: nothing was pushed.
Show as a table
| Week of | Commits |
|---|---|
| 24 Aug | 11 |
| 31 Aug | 8 |
| 7 Sept | 51 |
| 14 Sept | 23 |
| 21 Sept | 0 |
| 28 Sept | 6 |
| 5 Oct | 0 |
| 12 Oct | 94 |
| 19 Oct | 26 |
| 26 Oct | 85 |
Check it: the repository's own log, which these posts are written from
Every table knows which workspace it belongs to, and Postgres enforces it
The third commit of the day is a tenant schema with row-level security switched on. Every table that holds a customer's data carries a workspace id, and Postgres has a policy on each one that says a query may only see rows whose workspace matches the one the connection has declared. The application sets that declaration at the start of every transaction. A query that forgets to set it does not see everybody's rows; it sees nobody's.
The fourth commit is a test that opens two workspaces, writes a row into each, and proves the first cannot read the second's. That test is still in the suite. In September it caught two cross-tenant reads that were returning zero rows and saying nothing about it, which is exactly the failure the policy is designed to produce instead of a leak: a page quietly empty rather than a page quietly full of the wrong person's data. Later still, a second test asks Postgres which tables carry a workspace id and refuses to pass if any of them has no policy, because a list of tables to check is only ever as good as the last time somebody remembered to add to it.
A login with no password, and a worker that reports in
The login is a magic link: an email, a signed token, a session. No password table to protect, and nothing a customer could reuse from another site. Later that became a proper auth library with Google sign-in beside it, but the shape held: this product holds as little of a person's secrets as it can, which matters more than usual when the next thing it will ask for is access to their LinkedIn.
The worker is the half of the product that does the work when nobody is looking: it will find people, write to them, send, poll for replies. On day one it did none of that. It started, wrote a heartbeat into Redis every few seconds, and stopped. That heartbeat is the oldest alarm in the system. Two months later the admin report would flag a worker that had been silent for ten hours, and it did so because of a commit made before there was anything for the worker to do.
What I would say to someone starting the same thing
- Write the row-level policy before the first feature, and write the test that proves it the same day. Adding it later means auditing every query you have written since, and you will miss one.
- Make the background worker report that it is alive before it does anything else. The silence of a dead worker is the most expensive silence this kind of product has.
- Put the deploy in on day one. The Dockerfile from this week is still the one in use, and the first real deploy, two weeks later, was still a mess. It would have been a worse mess without it.
The limits this product runs under were not written this week; they were written as constants in the second week and have not moved since. They are on the limits page. The point of this post is that the product's two biggest promises, your data stays yours and your account is handled carefully, were being built before the product had a single feature, because they are not features.