1. Wanting to deliver the morning recitation to a team
There is a personal morning recitation app. Each morning, you select one phrase from your registered collection and recite it aloud. You record it, accumulate it, and something shifts over time. The mechanism is simple, but sustained practice produces real change.
Could this be brought to organizations? Company mottos, founding principles, words from a founder — delivered each morning to every employee's phone. As a replacement for the morning meeting, or as a complement to it.
The mechanism seemed straightforward. An administrator registers phrases. Employees open the app. That's it. But the requirement to "deliver different phrases to each tenant" turned out to raise deeper design questions than expected.
2. The first design — create a repository per tenant
The first idea was to create a separate repository for each tenant.
Organization A signs up → repository auto-created → published via GitHub Pages
Accessing Organization B's URL serves configuration specific to Organization B
It was built. When payment completed, a webhook fired, GitHub Actions created the repository, enabled Pages, and wrote the initial configuration files. Minutes later, a dedicated URL was live.
It worked. But the problem appeared immediately.
Every time the app code was updated, the changes wouldn't propagate to existing tenant repositories. As tenants multiplied, so did the repositories to manage. At one hundred tenants, one hundred repositories. At a thousand, a thousand. A growing mountain of copies with code and configuration intertwined.
3. The question — what actually differs per tenant?
Stepping back to think. What truly differs between tenants?
The app code is identical across all tenants. The UI, the logic, the notification system — all the same. What differs is only the app name, the phrases to deliver, and a handful of configuration values.
Gathered together, that fits in a single JSON file.
app_name: "Morning Recitation — Example Corp"
preset_words: [{ title: "Company Motto", text: "Honest, bold, swift." }]
lock_preset: true
That's all. A tenant was this one JSON file.
4. The new design — one repository, just add a directory
The design was changed.
Create one public repository to centrally manage all tenant configurations. When a new tenant is added, simply add a directory to that repository and place a tenant.json inside. The app itself doesn't change.
tenants/
abc12345/tenant.json ← Organization A
xyz67890/tenant.json ← Organization B
...
On startup, the app reads the tenant ID from the URL parameter and fetches the corresponding tenant.json. That alone transforms it into a dedicated morning recitation app for that tenant.
Updating the app code propagates instantly to all tenants. Repositories stop multiplying. Configuration and implementation are completely separated.
5. Deletion, updates, billing — all converge on file operations
The elegance of this design is that every operation related to tenants converges on file operations.
Creating a tenant means adding a tenant.json. Updating settings means rewriting that file. Deleting a tenant means removing that file.
Update queues are also stored in a different directory of the same repository. When an administrator presses "save and deliver," the Cloudflare Worker writes a JSON file to the queue. Processing runs every minute and reflects changes in tenant.json. Even if seven hundred organizations simultaneously save their settings, the Worker simply adds to the queue and returns immediately. No bottleneck.
What appeared complex reduced to reading and writing files.
6. The invoice payment question
Card payments were automated. But larger organizations often cannot use card payments. Purchase orders, procurement departments, invoice-based billing — there are people who operate within those cultures, without exception.
The first instinct was to handle it with "please contact us." But that creates a situation where someone who applied for invoice payment doesn't know how to proceed. The fact of the application, the tenant provisioning, and the payment confirmation float separately, unconnected.
The solution was to issue an order code at the time of application. Whether by card or invoice, an order code is generated at the start. That code connects the application, the configuration, the payment confirmation, and the tenant launch. Only the timing of processing differs; the flow is the same.
7. Design purity becomes operational simplicity
Building multi-tenant support reinforced something. Complexity accumulates from accumulated design choices.
When the first choice was "create a repository per tenant," every kind of complexity cascaded downstream. Code duplication, difficulty of updates, management overhead. One choice created dozens of problems.
When the design changed to "only one JSON file differs," the cascade stopped. What changes and what doesn't became clear. What doesn't change stays in the app itself; only what changes is extracted into JSON.
When design purity is high, code grows shorter, explanations grow shorter, operations grow lighter. When a morning recitation app's configuration can be expressed in just a few lines of JSON, that is evidence of high design purity.
A tenant was one JSON file. The code doesn't change. Only the words differ, from organization to organization.
TokiStorage is a project aimed at democratizing proof of existence — preserving voice, image, and text for a thousand years.
Learn about TokiStorage Read all essays