← Back to Alito

The Unframed Story

Everything you need. Nothing you don't.

How a side project became a 160K+ line codebase, and why engineering quality matters.

Rough First Draft

A few folks have started asking me what Unframed is. I don't know what it is exactly, but it's pretty good. It doesn't have any close kin, and I certainly haven't perfected talking about it yet. But here's the gist.

What I plan to do with it is even less mature. It's an evolving story.

In the Meantime

Alito coin
Unframed is not Alito.
What is Alito?

About two years ago, I started writing software for myself. I didn't know what domain problem I wanted to tackle yet. Seemed efficient to ponder that simultaneously with writing some plumbing code that I knew I was going to need.

For example, pretty sure folks are going to want to be able to sign in. At my last company we were paying six figures to Auth0 for sign-in functionality. And even so, it couldn't quite allow users to sign in all the ways we wanted. Everybody needs sign-in. It's not that hard.

So I built it. All the ways someone might want to sign in are supported. Passwordless login with stateless HMAC-signed tuples. Magic links — five types of them: redirect, login, login-then-redirect, email verification, and tracking pixels. OAuth with CSRF state tokens. Scrypt password hashing with per-user salts. A switchable token strategy so you can swap between transparent HMAC tokens and JWTs at deploy time. Plus PII encryption on the stored credentials. Plus secure logging that doesn't leak tokens into your log files. Plus rate limiting. Plus a sign-in audit ledger that records every attempt — success or failure, IP address, user agent, which auth method was used. Plus the tests. Done.

I still didn't know what problem I wanted to solve. But any solution is going to require transactional email. So I built that. SMTP integration, templating, delivery confirmation, retries. Done.

Still didn't know the problem. But any solution is going to need a way to store and query data without handwriting SQL for every operation. So I built a repository layer with parameterized queries, tenant scoping, and transaction management. Done.

Still didn't know the problem. But any solution is going to need authorization — who can see what, who can do what. So I built that. Relationship-based access control, declarative rules, role resolution. Done.

Still didn't know the problem. But any solution is going to need background jobs, scheduled tasks, health checks, structured error handling, a dependency injection container, an OpenAPI contract, feature flags, observability, audit logging…

And that's how it started.

Eventually enough was in place that I could start addressing my own problems. Calendly costs me $20/month and has feature gaps. So I solved calendaring. Task management was a mess across three apps. So I built that. Contact management, email tracking, URL shortening, GenAI connectivity — each time the same pattern: identify the friction, build it right, move on.

Fast forward two years: 160K+ lines of hard-working, domain-agnostic code. 270+ services, each with a single responsibility and a dedicated interface. A strong foundation that can power most every 80/20 tech-enabled SaaS product.

But it's not a framework (hence "unframed"). It's simply the first 160K lines you're gonna need no matter what you're starting.

"I didn't know what I wanted to build. So I built everything I knew I'd need, and I built it right. Two years later, I still don't regret a single line."

The 15% That Pays for Itself

Patterns emerged. In the projects I had control over, I invested the extra 15% to do it largely right the first time. And in those projects, the results spoke for themselves - testability, onboardability, maintainability, all the -ITYs.

The projects that skipped that foundation work? Nineteen months in, they were buried - 70 cents of every dollar going to service technical debt instead of building new capability.

So while I was figuring out the direction, I figured: might as well build this foundation right from the start. Even if I didn't know where I was going, I knew I didn't want to arrive there with a codebase I couldn't maintain.

I've started investing in companies who don't have a codebase yet - helping them get started with this. It's a low-to-no-debt, six-month head start. Nothing flashy, just what you need.

What's In the Box

State-of-the-art. But not cutting edge. Cutting edge is too new on the hype curve. And the science project R&D factor is an expense your efficiency-or-bust startup could do without.

It's a thousand little things done right the first time. (Drag to reorder — you decide what matters.)

⋮⋮
Adaptable authentication
Passwordless login, magic links, OAuth, SSO, switchable token strategy
⋮⋮
ReBAC + CanCan authorization
Zanzibar-inspired relations + Rails-style declarative auth
⋮⋮
Testing as first-class citizen
Transaction-wrapped auto-rollback, random order, zero beforeAll(), test tags
⋮⋮
Logging as first-class citizen
Named loggers, method prefixes, d4l() serialization, leave-in philosophy, grep any log line back to its source
⋮⋮
PII security in a box
AES-256-GCM at rest, HKDF key derivation, Encrypt-then-MAC, PII access audit log
⋮⋮
Branded types
UserId is not a PrincipalId — compile-time enforcement, not runtime hope
⋮⋮
Service oriented (not microservices)
270+ services, each with an interface. Repository → Service → Controller. No network hops, no YAML.
⋮⋮
Multi-tenancy baked in
Every table, every query, tenant-scoped from day one. Retrofitting this is a rewrite.
⋮⋮
Ambient request context
Every service knows who's asking without being told. Express never leaks past the controller.
⋮⋮
Discussable pattern
Polymorphic threaded discussions on any entity
⋮⋮
Universal resource tags
Tag anything, implicit collections, cross-feature discovery
⋮⋮
Beacon system
Tracking pixels + short URLs + self-view detection
⋮⋮
Encrypted vault
Passwords, TOTP, secure notes, RSA keypair sharing
⋮⋮
Double-entry ledger
Holds, transfers, reconciliation
⋮⋮
Q&A system
Scheduled daily questions to teams, chained follow-ups
⋮⋮
Topics with mana-based voting
Threaded discussions with constrained voting resources — elevates moderation, not volume
⋮⋮Signin ledger (audit trail)
⋮⋮Masquerade as users (dev only)
⋮⋮
Stat engine
Capture, filter, downsample, SVG sparkline rendering
⋮⋮Unique Visitor Token (Encrypt-then-MAC)
⋮⋮
The handful of design patterns that matter
Program to an interface, KISS, Find what varies and encapsulate it, Favor composition over inheritance
⋮⋮
Only 27 runtime dependencies
160K lines of code. 27 external dependencies. Bring yours without conflict.
⋮⋮
Boring technology
Hard-working, battle-tested, no ORM magic
⋮⋮
No magic
Follow the code when things break. No hidden behaviors.
⋮⋮
Clean interfaces
No context slop in method signatures
⋮⋮
Normalized exceptions
Crash on programmer error, handle operational errors deterministically
⋮⋮
Progressive disclosure
Users only see what applies to them — login, nav, collections. Ambient assistant surfaces one micro-action at a time.
⋮⋮Dependency Injection (manual, no framework)
⋮⋮
Migration system
golang-migrate, factory reset, deterministic schema
⋮⋮
Background job queue
SolidQueue-compatible, retry with backoff, dead letter queue
⋮⋮
Healthcheck-triggered scheduler
No external cron — Route 53 pings trigger async tasks
⋮⋮Graceful shutdown (coordinated teardown)
⋮⋮Email obfuscation (HTMX anti-scraping)
⋮⋮
Calendly drop-in replacement
Free time calc, availability overrides, holidays, booking requests
⋮⋮
Email infrastructure
SMTP via SES, templates, send caps, crash notifications
⋮⋮JSON-ify LLM responses (structured output)
⋮⋮OTEL capture to S3
⋮⋮Key-value store (sys_setting)
⋮⋮
Task management
Archetypes, pools, time buckets, dependencies
⋮⋮
Docker support
Full containerized dev/prod environment
⋮⋮
Sheet data provider
CSV, Google Sheets, in-memory — same interface
⋮⋮
Feature flag service
Runtime toggles, per-feature enable/disable
⋮⋮
LLM Integrations
ChatGPT, Claude, Bedrock, Gemini. Use them day 1.
⋮⋮
API Integrations
Google Calendar, Gmail, Sheets, Contacts, AWS, Linear
⋮⋮
Multi-backend fan-out
One event → Postgres, S3, BigQuery, Kafka, Mixpanel
⋮⋮
No vendor lock-in
All you need is a Linux server and Postgres
⋮⋮
Own-your-data Analytics
Postgres, BigQuery, S3, Dynamo — use in business logic
⋮⋮
Happening system
OpenTelemetry-aligned event tracking with automatic enrichment. Data is local and normalized — business logic can trigger on and act on this knowledge.
⋮⋮
Push notifications
Telegram, SMS, email — pluggable channels
⋮⋮
Turn-based game engine
Up The River + Texas Hold'em, bots, room multiplayer, WebSockets
⋮⋮
Talk-to-my-data
AI-powered natural language database queries
⋮⋮
Document store
Pluggable NoSQL-like: Postgres JSONB, DynamoDB, or MongoDB
⋮⋮Lightweight CRM
⋮⋮Pixel tracking + short URLs + analytics
⋮⋮Right ID for the job (v7, nano, slugs)
⋮⋮Timezones done right the first time
⋮⋮Strict TypeScript
⋮⋮Natural language date parsing
⋮⋮IP-based geolocation
⋮⋮Flashcard system with TTS
⋮⋮
Principal relationships
User-to-user, user-to-platform, user-to-object
⋮⋮
CLI toolkit
encrypt-email, talk-to-my-data, codebase-scores, REPL console
⋮⋮
Breadcrumb tool
AI-powered codebase navigation with concept extraction
⋮⋮
Codebase scores
Automated SOLID and architecture metrics
⋮⋮
Photo albums
S3 uploads, tags, comments, sharing invitations
⋮⋮
Polls
Multiple-choice, group-scoped, closeable. Quorum and most-vote-wins.
⋮⋮Job posting system
⋮⋮Startup company scoring
⋮⋮Lambda architecture support
⋮⋮RAFT consensus / distributed locking
⋮⋮Daily standup sessions
⋮⋮Typing practice with WPM tracking
⋮⋮Client sync (offline-first)

What's (Specifically) Not In the Box

Some things were deliberately left out.

Resume Driven Development
Mass dependencies
ORM magic
Bloat (only Only 27 external runtime dependencies)
Vendor lock-in
Clever one-liners
Convention over configuration
Technical debt
Shared test state
beforeAll()
Magic
Shiny things
Everything depends on everything

First Class Citizens

Most codebases treat these as afterthoughts. Unframed treats them as load-bearing walls.

The -ITYs

Most codebases optimize for short-term shipping speed and treat quality attributes as nice-to-haves. Unframed treats them as load-bearing walls — each one is enforced by convention, tooling, and code review. Several of these are measured by deterministic scoring algorithms that run against the codebase and produce repeatable results.

  • Testability. Testable code isn't about writing more tests — it's about designing code that's easy to test. Constructor injection means every dependency is swappable with fakes or mocks. Interface Segregation keeps contracts small so tests only depend on what they need. Single Responsibility means each class does one thing, so each test exercises one behavior. Tagged tests let you compose test suites on the fly — A-tier vs. B-tier, unit vs. integration, authentication vs. authorization, fast pure-logic vs. needs-database. Run exactly the tests that matter for your change in seconds. Transaction-wrapped tests auto-roll back — zero shared state, zero cleanup code, zero beforeAll(). Tests run twice: random order then normal. If order matters, the tests are broken.
  • Modularity. 270+ services, each with a dedicated interface file and an Impl file. Manual DI wiring — no framework, no decorators, no classpath scanning. Every module is self-contained: co-located interface, implementation, and types. Swap any piece without ripple effects.
  • Maintainability. Single Responsibility enforced — one reason to change per class. Co-location means related code lives together. Interface-first design means you modify implementations, not contracts. The codebase gets easier to maintain as it grows, not harder.
  • Understandability. No abbreviations. SystemConfigurationService, not SysConfigSvc. Every method starts with an action verb. Variable names match class names. 1,200+ lines of PHILOSOPHY.md, 780+ lines of TECHNICAL_GUIDE.md, 160+ conversation-starter docs.
  • Upgradeability. Only 27 external runtime dependencies — each one chosen deliberately, each one replaceable. No ORM migration chains to manage. No framework version lock-in. Vendor abstractions behind interfaces mean upgrading or swapping a provider is a one-file change.
  • Debuggability. Leave-in logging with named loggers, method prefixes, and the Greppable Log Prefix Rule. Every log line traces back to its source with a single grep. Objects serialized with d4l() — no [object Object], no missing context.
  • Readability. File names match what's inside. HoldemGameRoomService.ts contains HoldemGameRoomService. No clever tricks, no magic, no indirection that requires a debugger to follow. Code reads like prose, not a puzzle.
  • Clarity. Every logger is named module.ClassName. Every log line is prefixed with its method name. Log text is unique before interpolation (GLPR). No ambiguity about what happened, where, or why.
  • Tenability. Every architectural decision is documented and defensible. PHILOSOPHY.md explains the why. TECHNICAL_GUIDE.md explains the how. Conversation-starter docs capture the tradeoffs considered and rejected. No decisions by accident — only decisions by intent.
  • Sustainability. No tech debt treadmill. The database schema is reborn from empty hundreds of times via factory-reset — if it can't be rebuilt cleanly, it's fixed. Leave-in logging means you never re-add instrumentation. Interface-first means you never rewrite callers when implementations change.
  • Repeatability. Deterministic tests, reproducible builds, factory-reset from empty. The database is rebuilt from migrations and seed data — not nursed along with manual patches. Codebase quality scores are computed by algorithms, not opinions. Same inputs, same outputs, every time.
  • Fixability. When something breaks, you find it fast. Greppable log prefixes, named loggers, method-level tracing, and d4l() serialization mean the log output tells you what broke, where, and why — before you open an editor.
  • Pay-it-forward-ility. Today's work makes tomorrow's easier. Leave-in logging prevents the next person from re-debugging the same problem. Interface-first design means the next feature plugs into existing contracts. Every conversation-starter doc saves a future engineer from re-deriving the same conclusion.
  • Traceability. From a production log line, you can follow the path to the exact method, the exact service, the exact request. Three exception types with zero ambiguity: NotFoundException, ProgrammerErrorException, StateException. Every event flows through the Happening system with who, what, when, where.
  • Onboardability. README, PHILOSOPHY.md, TECHNICAL_GUIDE.md, CLAUDE.md, and 160+ conversation-starter documents. The codebase explains itself. A new engineer — or an LLM — can get productive without a walkthrough.
  • Teachability. The codebase is its own curriculum. Consistent patterns everywhere — learn how one service works and you know how all 270+ work. Interface → Impl → DI wiring → Controller. The same shape, every time, in every module.
  • Swappability. Every service is behind an interface. Every dependency is injected through constructors. The DI container is the only file that knows which Impl backs which interface. Swap Postgres for DynamoDB, swap Claude for GPT, swap SMTP for a test double — one file changes, zero consumers notice. The plain-function pattern stops working when you need testability, swappability, or multi-environment configuration. Interfaces + DI is the answer.
  • Agility. Program to interfaces, not implementations. No framework lock-in, no database lock-in, no cloud lock-in. Pivot without being weighed down. The codebase bends without breaking.
  • Reusability. The Discussable pattern makes any entity commentable. ResourceTag makes any entity taggable. The Happening system tracks events on anything. Build a pattern once, use it across every module.
  • Maturity. Battle-tested in production with real users and real data. No experimental frameworks, no resume-driven architecture. Every dependency is proven. Resources go toward value creation, not R&D novelty.
  • Extensibility. Add features without rewriting. New modules plug into existing infrastructure — authentication, logging, DI, testing, tagging, discussions — all available from day one. The 18th feature is as easy to add as the 3rd.
  • Simplicity. Only 27 external runtime dependencies. Raw SQL instead of an ORM. Manual DI instead of a framework. No Docker, no Kubernetes, no microservices. One server, one database, one codebase. Complexity is the enemy — every added layer must justify its existence.
  • Developer Productivity & Enjoyability. Minutes to start coding, not days fighting infrastructure. No LocalStack, no Docker Compose, no environment babysitting. make run-dev and you're building features. Development should be enjoyable, not a chore.
  • Robustability. The codebase handles failure gracefully. Programmer errors crash immediately — no silent corruption. Operational errors are caught and handled deterministically. The database schema rebuilds from empty. The test suite runs in random order. If something can break, it breaks loudly, early, and in a way you can fix.
  • Usability. Progressive disclosure means users only see what applies to them. No "sign up vs. sign in" decision — enter your email and the system figures it out. Navigation adapts to who you are. Features reveal themselves as you use them, not all at once in an overwhelming dashboard.
  • Positivity. The codebase is a place people want to work. Consistent patterns reduce cognitive load. No tribal knowledge required. No landmines. No "don't touch that file." When every module follows the same shape, confidence replaces anxiety — and confident developers ship better code.
  • Productivity. The architecture gets out of your way. New features plug into existing infrastructure — authentication, logging, DI, testing, tagging, discussions — all available from day one. The 18th feature is as easy to add as the 3rd. A developer's time goes toward solving the problem, not fighting the codebase.

Leave-In Logging

The industry pattern is: add logging to debug, fix the bug, remove the logging. We think that's backwards.

  • Every log statement is written as if it will be in production forever — because it will be.
  • Correct log level from the start. No "temporary" console.log. No upgrading debug to info just to see output.
  • Every logger is named after its module. Every log line is prefixed with the method that wrote it.
  • Objects are serialized with d4l() — no [object Object], no accidental PII leaks.
  • GLPR (Greppable Log Prefix Rule): log text is unique before interpolation, so you can grep any line back to its source.
  • The logging you add while debugging a problem is the logging that prevents the next person from needing to debug it.

Testing

Not "we have tests." Every test is designed to be trustworthy.

  • Every test runs in a transaction that auto-rolls back. No shared state. No cleanup code. No flaky tests.
  • Tests run in random order. If order matters, your tests are broken.
  • Zero beforeAll() in the entire codebase — not one. Each test sets up exactly what it needs.
  • Test tags (speed:purelogic, needsRdbms) let you run the right subset in seconds.

Exceptions

Two kinds of errors, two handling strategies, zero ambiguity.

  • Programmer errors crash immediately. They are never caught. If your code has a bug, you find out now — not three layers up.
  • Operational errors (not found, bad state) are handled deterministically with normalized exception types.
  • Three types: NotFoundException, ProgrammerErrorException, StateException. That's it.

Program to an Interface

Not just "we use interfaces." Every service has one, and nothing depends on a concrete class.

  • 270+ services. Every one has a dedicated interface file and an Impl file. No exceptions.
  • Dependency injection is manual and explicit — no framework, no decorators, no classpath scanning.
  • You can swap any implementation without touching the code that uses it.

Security

Not bolted on after a breach. Designed in from line one.

  • AES-256-GCM encryption at rest. HKDF key derivation. Encrypt-then-MAC for cookies and tokens.
  • PII access audit log — every read of sensitive data is recorded.
  • Parameterized queries everywhere. No string concatenation in SQL. Ever.

IDs & Time

Two things most codebases get wrong early and pay for forever.

  • Right ID for the job: UUID v7 for database rows (time-sortable), nanoid for URLs, slugs for humans.
  • Branded types enforce compile-time distinction — UserId is not a PrincipalId, even though both are strings.
  • DateProvider service — no raw new Date(). Time is injectable and testable from day one.
  • Timezone-aware from the start. Not retrofitted after the first international user.

Semantic Naming

Most codebases abbreviate everything. Then new engineers spend their first month decoding tribal shorthand.

  • No abbreviations. TransactionContext, not txCtx. SystemConfigurationService, not SysConfigSvc.
  • Every method name begins with an action verb: createUser, findByEmail, calculateFreeTime.
  • Variable names match class names: TransactionContexttransactionContext. No guessing what anything is.
  • File names match what's inside: HoldemGameRoomService.ts contains HoldemGameRoomService. Always.

Observability (OTEL)

Most teams bolt on observability after something breaks in production. Here it's built into the event model.

  • The Happening system is OpenTelemetry-aligned from the ground up — structured events with automatic enrichment.
  • Every happening captures context: who, what, when, where, from which IP, which session, which request.
  • Multi-backend fan-out: one event goes to Postgres, S3, BigQuery, Kafka, Google Sheets, or Mixpanel — your choice.
  • OTEL traces captured to S3 for long-term analysis. Not just dashboards — queryable data you own.

Analytics

Most analytics live in a third-party dashboard you can look at but can't use. Ours live in the database where your code runs.

  • Own-your-data: analytics land in Postgres, BigQuery, S3, and DynamoDB — not just a vendor's SaaS dashboard.
  • Use analytics in business logic. "Show users who were active this week" is a SQL query, not an API call to Mixpanel.
  • Stat engine with capture, filtering, downsampling, and SVG sparkline rendering — all in-house.
  • Beacon system: tracking pixels, short URLs, unique visitor tokens, self-view detection — built in, not bolted on.

Commodity LLM Connectivity

Most codebases treat AI as a feature to add later. Here it's plumbing — available everywhere, from day one.

  • Four providers wired in: Claude, ChatGPT, Bedrock, Gemini. Swap models without changing calling code.
  • Structured output extraction — JSON-ify any LLM response into typed objects your services can use.
  • Talk-to-my-data: natural language queries against your own database. Not a demo — a production CLI tool.
  • AI is a service dependency like any other: injected, interfaced, testable, swappable.

Progressive Disclosure

Most apps show everything to everyone and hope users figure out what's relevant. Progressive disclosure means users only see what applies to them, when it applies to them.

  • Login flow is progressive: enter your email, we figure out whether you're new or returning, then show the right next step. No "sign up vs. sign in" decision for the user to make.
  • Navigation adapts: anonymous visitors see public content. Logged-in users see their tools. Admins see admin controls. Same URL, different experience.
  • Collections and features reveal themselves as you use them — not all at once in an overwhelming dashboard.
  • The pattern is baked into the framework, not bolted on per-feature. Any new feature inherits progressive disclosure automatically via the authentication and authorization layers.

Vendor Independence

The last two decades coached us to trade build-it for buy-it. We traded moderate development costs for overhyped "solutions as services" — which come with expensive integration, orchestration, vendor lock-in, and a convenience charge.

What have you been convinced you need just to get started? Auth0 for login. Mixpanel for analytics. Google Analytics for tracking. Elasticsearch for search. New Relic or Datadog for monitoring. SendGrid for email. LaunchDarkly for feature flags. Sentry for error tracking. Calendly for scheduling. Stripe Billing before you have revenue. Redis and Sidekiq before you have traffic. MongoDB Atlas or Supabase because "Postgres doesn't scale." Segment to glue it all together. Docker and Kubernetes before you have a second server. Terraform before you have a second environment. And a DevOps hire to keep the lights on.

Unframed replaces all of them. Authentication, analytics, email, scheduling, feature flags, error handling, job queues, observability, event tracking, search — it's all here, already built, already tested, already working. On a single Linux server and Postgres.

And it can scale to millions of users before you need to consider what's next. Unframed is the perfect Product-Market Fit Finder — the codebase that lets you focus on finding your market instead of stitching together vendors.

  • No AWS lock-in. S3, SES, DynamoDB, and Bedrock are used but abstracted behind interfaces — swap the implementation, keep the contract.
  • No framework lock-in. Express is the HTTP layer, but business logic doesn't know or care.
  • No database lock-in. Raw SQL with a thin repository layer. No ORM migration path to manage.
  • Only 27 external runtime dependencies. You can read the full list in under a minute and understand every one.

Done Right the First Time

The industry ships fast and fixes later. "Later" usually means "never." Unframed took the slower path — doing it properly from day one — because shortcuts compound into the kind of debt that eventually stops you cold.

Database Schema — Reborn from Empty, Hundreds of Times

The standard approach: write your initial schema, then accumulate ALTER TABLE migrations forever. After a year you have 200 migration files and nobody knows what the schema actually looks like without running them all.

  • One canonical schema file. Every change — column rename, new table, constraint tweak — is made directly in that file, then make factory-reset rebuilds the entire database from empty.
  • This schema has been recreated from scratch hundreds of times. Each rebuild is a clean CREATE TABLE, not an ALTER stapled onto history. The schema you read today is the distilled result of hundreds of iterations, each one starting from zero.
  • Even production can be rebuilt: precious data is backed up, the schema is recreated clean, data is restored. This isn't theory — we do it routinely.
  • The result: the schema reads like documentation. No archaeological layers. No "this column used to be called X." No migration that adds a column and a later migration that renames it.
  • GENERATED ALWAYS AS computed columns. Trigger-managed timestamps. UUID v7 primary keys. Proper normalization. All from day one — not retrofitted.

PII Encryption at Rest

Most startups say "we'll add encryption when we need to comply with something." We encrypted PII fields the day they were first stored.

  • AES-256-GCM with HKDF key derivation — not "we'll add encryption later."
  • PII access audit log — every read of sensitive data is recorded, not just writes.
  • No compliance requirement forced this. We did it because it was the right thing to do with other people's data.

Pseudonymized Ownership

The spending tracker knows who owns each transaction. But it doesn't store that relationship in the obvious way.

  • HMAC-SHA256 pseudonyms instead of raw user IDs. Even if the database leaks, you can't trivially map transactions to people.
  • The pseudonym system is built into the data model — not a privacy layer bolted on after a regulator asked questions.
  • Most startups don't think about this until they're preparing for SOC 2. We built it before we had our second user.

Eating Our Own Dogfood

Most platforms are built by people who don't use them. The team builds a feature, ships it, moves on. They never feel the friction. Unframed is different — every feature is used daily for real work. The builder is the user, and the feedback loop is instant.

Spending Tracker

Real Chase and Citibank statements. Real grocery spending. Real subscriptions.

  • The recurring payment detection feature exists because we looked at our own spending page and thought "I know I'm paying for more subscriptions than this shows."
  • Receipt parsing was built by scanning actual grocery receipts from weekly runs to Jewel-Osco.
  • Merchant categorization was refined by looking at our own uncategorized charges and asking "why didn't this get tagged?"

Vault

The team's actual passwords, API keys, and secrets live in the Unframed vault.

  • AES-256-GCM encryption with client-side key derivation — not because a spec required it, but because our passwords are in there.
  • We didn't outsource to 1Password or Bitwarden. We built it, we use it, we trust it with our own credentials.

Card Games

Hold'em and Up The River — built, then played with friends on game night.

  • The bot AI (Tight Teresa, Calling Carl) exists because we needed opponents when nobody else was online.
  • The WebSocket reconnection logic was debugged during an actual poker game when someone's phone went to sleep.
  • Starting chip counts, blind structures, and game pacing were all tuned by playing real hands — not by guessing at parameters.

Everything Else

It's not just the marquee features. The small tools get used daily too.

  • 700+ real bookmarks in production. Not test data — actual links saved and retrieved.
  • The CLI tool manages production deploys, sends notifications, and runs seeds. We use it every day.
  • Telegram notifications tell us when deploys finish while we're away from the desk. Built because we needed it.
  • The Happenings dashboard is read daily. Google Sheets capture exists because we wanted analytics in a spreadsheet we already had open.

Build, Don't Lease

The industry says "buy." But subscribing to SaaS vendors isn't buying — it's leasing. You don't own anything. When the vendor raises prices, sunsets features, gets acquired, or goes down, you're exposed. Auth0 was acquired by Okta — pricing changed. Heroku killed their free tier. Google killed Google Domains. Parse shut down entirely.

Unframed builds instead of leasing. The code doesn't get acquired. It doesn't raise prices. It doesn't sunset features. The only cost is maintenance — and you're maintaining it anyway because you're building on top of it.

14 Capabilities We Own

Each of these is a line item on someone else's vendor bill. For Unframed, they're just code we wrote and own outright.

  • Authentication — scrypt passwords, passwordless tokens, transparent auth. Not Auth0 ($35-240/mo).
  • Monitoring & Observability — OTEL traces to S3, structured logging, health checks. Not Datadog ($100-300/mo).
  • Product Analytics — Happening system + Postgres + multi-backend fan-out. Not Mixpanel ($280/mo at 2M events).
  • Error Tracking — structured logging + OTEL spans. Not Sentry ($29/mo).
  • Email — Nodemailer + AWS SES. Not SendGrid ($20-90/mo).
  • Feature Flags — environment-driven FeatureFlagService. Not LaunchDarkly ($40-60/mo).
  • Job Queues — SolidQueue on Postgres. Not managed Redis + Sidekiq ($15-50/mo).
  • Search — Postgres full-text search with GIN indexes. Not Algolia ($50-200/mo).
  • Password Management — Vault with AES-256-GCM. Not 1Password ($16/mo).
  • CRM — contact manager + outreach tracking. Not HubSpot ($40-60/mo).
  • Scheduling — Google Calendar integration. Not Calendly ($24/mo).
  • Notifications — Telegram + SMS + in-app. Not Twilio + OneSignal ($10-30/mo).
  • CI/CD — Makefile + rsync + systemd. Not CircleCI ($15-30/mo).
  • Container Orchestration — single Linux server + systemd. Not EKS ($73/mo for the control plane alone).

The Economics

Unframed's total infrastructure cost: ~$50/month. One EC2 instance, AWS SES for email, S3 for storage.

  • At small scale (2 people, low usage): the vendor bill would be ~$280/mo. Annual savings: ~$2,700.
  • At moderate scale (10K users): ~$1,150/mo in vendor costs. Annual savings: ~$13,000.
  • At growth scale (100K+ users): vendors alone run $3,000-5,000/mo. Annual savings: $33,000-57,000.
  • SaaS costs scale with success — the more users you get, the more you pay vendors for the privilege of serving them. Owned infrastructure stays flat until you genuinely need to scale.

Postgres Scales Further Than You Think

"Postgres doesn't scale" is vendor marketing. OpenAI serves 800M users on a single-primary Postgres deployment. Figma scaled to 4M users on Postgres before needing changes.

  • A single well-tuned RDS instance handles 500K-1M+ registered users before any homegrown solution needs revisiting.
  • At $250/mo (db.r6g.large), Postgres handles 200K registered users with all of Unframed's services running fine.
  • The first thing to upgrade — job queues to Redis — costs $15-50/mo and is a swap behind an existing interface.
  • The architecture is designed to let components graduate independently. Everything is behind interfaces — when something outgrows Postgres, you swap the implementation, not the architecture.

The Philosophy

Unframed started as a simple question: What would happen if we built software the "right way" instead of the fast way?

In an industry obsessed with shipping fast, cutting corners, and accumulating technical debt, I wanted to know if there was another path. Could you build a serious codebase while still being productive? Could you write tests for everything without slowing to a crawl? Could you maintain clean architecture while iterating quickly?

The answer, it turns out, is yes. But it requires discipline, good tools, and a willingness to think long-term.

"The best code is code that's easy to delete, easy to understand, and easy to change. Everything else is premature optimization."

The Principles

Unframed is built on a set of principles that prioritize maintainability and onboardability over cleverness, clarity over brevity, and tested behavior over assumed correctness.

Testability First

Every feature is designed to be testable from day one. If you can't test it, you shouldn't build it that way.

Explicit Over Implicit

We prefer verbose, obvious code over clever one-liners. Future-you will thank past-you for the clarity.

Program to Interfaces

Dependency injection isn't just a pattern - it's how we keep the codebase flexible and testable.

No Magic

When something breaks, you should be able to follow the code. No hidden behaviors, no convention-over-configuration surprises.

Everything You Need. Nothing You Don't.

Every feature exists because someone was overpaying for it, couldn't find it done right, or needed it and it didn't merit a standalone product. No bloat, no feature creep, no "just in case."

By the Numbers

What does "building it right" look like in practice? Here's what 2+ years of disciplined development produces:

1
Human
160K+
Lines of Code
270+
Services
Only 27
Runtime Dependencies
25
Years of Learnings

The codebase includes:

  • Passwordless authentication with magic links, JWT, OAuth, and CanCan authorization
  • AES-256-GCM encryption at rest with PII audit logging (HIPAA-ready)
  • Google Calendar + Gmail + Contacts + Sheets integrations
  • Multi-provider AI/LLM support (Claude, ChatGPT, Bedrock, Gemini) with natural language database queries
  • OpenTelemetry-aligned event tracking with multi-backend fan-out (Postgres, S3, BigQuery, Kafka, Mixpanel)
  • Turn-based game engine with two card games (Up The River, Texas Hold'em) and real-time WebSockets
  • SolidQueue-compatible background job queue with retry, dead letter, and scheduling
  • Encrypted vault, task management, budgeting/ledger, CRM, photo albums, and 20+ domain features
  • Universal resource tagging, polymorphic discussions, and cross-feature collections
  • Full CLI toolkit, Docker support, Lambda handlers, RAFT consensus, and REPL console

The Journey

2024 - The Beginning

Started as a personal project to explore TypeScript patterns and dependency injection. The goal: build a codebase that could scale without becoming unmaintainable.

Early 2025 - Finding the Patterns

Settled on key architectural decisions: explicit DI container, repository pattern for data access, service layer for business logic, controller layer for HTTP concerns.

Mid 2025 - Feature Explosion

Added calendar integration, real-time game engine, email tracking, encrypted vault, and more. Each feature built with the same disciplined approach. The codebase grew but stayed maintainable.

Late 2025 - Rails Integration

Added companion Rails apps for marketing sites, sharing infrastructure and learnings between ecosystems.

Early 2026 - Alito Launches

The Unframed codebase becomes the foundation for Alito - proving that well-built infrastructure can power real products. 160K+ lines, 270+ services, 29 MVP-ready tools.

2026 - Second Game Ships

Texas Hold'em poker built on the same game engine as Up The River - ~70% code reuse. The generic TurnBasedRunner, card utilities, and Room/Behavior/Mutator pattern proved their worth.

Why It Matters

In a world of quick MVPs and technical debt, Unframed represents a different approach. It's proof that you can:

  • Write tests for everything - and still ship features quickly
  • Use dependency injection - without ceremony or boilerplate
  • Maintain clean architecture - while iterating on features
  • Build real products - on solid foundations

The Unframed codebase isn't just infrastructure - it's a philosophy manifest in code. It's a bet that building things right pays dividends over time.

"Technical debt is just regular debt with extra steps. The interest compounds, and eventually you have to pay it back - usually at the worst possible time."

Two Sides of the Same Coin

Alito and Unframed aren't parent and child. They're two sides of the same coin.

Unframed is the reusable codebase — the patterns, the architecture, the 270+ services, the interfaces. Alito is a collection of lightweight tooling products that battle-test the underlying Unframed plumbing against real users and real data. Side benefit: over $100/month in SaaS subscriptions replaced so far — and every replaced tool is one more module proven in production.

Why These Features Exist

Every feature in Alito exists for one of three reasons:

  • Overpaid for it elsewhere. Calendly costs $20/month with feature gaps. 1Password costs $16/month. HubSpot costs $40/month. These are tools I was already paying for — and paying too much for things that should be simple.
  • Inadequate elsewhere. The SaaS version had limitations or workflows that didn't fit. The spending tracker exists because nothing handled family spending across multiple banks the way we needed.
  • Didn't merit a standalone product. A bookmark manager isn't a company. A typing practice tool isn't a product. But they're useful tools that any platform should be able to spin up in a day — if the foundation is there.

Features as a Forcing Function

Alito's features aren't just dogfooding. They're a forcing function for the modularity — all the -ilities — in Unframed.

  • To prove the encryption layer works, you need a vault that stores real passwords. Ours does.
  • To prove the WebSocket layer works, you need a real-time multiplayer game people actually play. We play Hold'em on it.
  • To prove the ledger works, you need a spending tracker importing real bank statements. We import ours weekly.
  • Without Alito, Unframed's interfaces would be theoretical. With Alito, every interface has been exercised, every pattern stress-tested, every module proven in production.

The Map

You can draw a direct line from each Alito feature to the Unframed modules it exercises:

Alito Feature Unframed Modules Pattern It Proves
Spending Tracker spending, ledger, standin, crypto Double-entry accounting, HMAC-SHA256 pseudonyms, recurring payment detection
Vault vault, crypto, piisecurity, audit AES-256-GCM at rest, HKDF key derivation, PII access audit log
Calendar calendar, addon, oauth, chrono Multi-provider OAuth, free-time calculation, timezone handling
Hold'em / Up The River holdem, uptheriver, websocket, bot Turn-based game engine with ~70% reuse, WebSocket multiplayer, NPC AI
Topics + Mana Voting topicofconversation, discussion, poll Constrained voting resources, polymorphic threaded discussions
Analytics / Magic Links stat, happening, beacon, pixel Event fan-out to 5 backends, sparkline rendering, tracking pixels
Bookmarks bookmark, resourcetag, collection Universal tagging, implicit collections, full-text search
Tasks / Just One Thing task, justonething, assessment Task archetypes, ambient micro-task surfacing, assessment engine
Contacts / CRM contact, crmlite, magiclink Contact-outreach tracking, magic link analytics, entity-scoped threads

Plus cross-cutting patterns exercised by every feature: authentication, leave-in logging, branded types, interface-first DI, transaction-wrapped testing, universal resource tagging, and the Discussable pattern.

The economics have shifted. It used to be more expensive to create software than to lease it. That's no longer true. Building a calendar integration is a week's work, not a quarter. Building a password vault is days, not months. The "just use a SaaS" advice was right when building cost 10x more than leasing. Now it's cheaper to build — and you own it forever.

What's Next for Unframed

"Operational intelligence and maturity is among our unfair advantages. One example of this is the Unframed codebase."

I'm exploring what it would look like to partner with an early-stage fund or incubator to do this right - giving very-early-stage teams (the ones worth betting on) a strong software foundation and a real head start.

Imagine: all 20 companies in a cohort starting with a 9-month head start. The first 160K lines of code everyone needs, already written, already battle-tested, already working.

No technical debt from day one. Just a clean foundation to build on.

If you're an investor running an accelerator or incubator, or a founder looking for a head start: .

Ready to try tools built the right way?

Explore Alito Tools