02 / Confidential client · US · 2019-present
Seven years on call for one platform
A compliance platform serving 3,406 fleet companies, built and run by one person for seven years.
- Role
- Solo Developer & Architect
- Timeline
- 7+ years
- Type
- DOT compliance SaaS
- Stack
- Laravel 10 · Vue 3 · Inertia.js · Tailwind CSS · MySQL · Redis · Stripe · Pusher
At a glance
- Multi-tenant DOT compliance SaaS serving 3,406 fleet companies and 8,859 active employees. I have built and run it alone since April 2019.
- Five authentication guards separate super admins, staff teams, company admins, company teams, and multi-company agents across a ~75-table schema.
- Stripe at three levels: subscriptions, per-order charges, and one-time fees, all reconciled through webhooks.
- Currently migrating Blade + Vue 2 to Inertia + Vue 3 page by page, on a live system, with both stacks in production.
01 / Context
Compliance is a product that can't be wrong
US trucking companies are legally required to run DOT drug-and-alcohol testing programs: pre-employment tests, random pools, post-accident protocols, FMCSA Clearinghouse queries, motor-vehicle-record checks. The platform turns that regulatory maze into software. Fleet companies enroll drivers, order tests, collect e-signatures, and stay auditable.
The client hired me off Upwork in April 2019 for what could have been a small job. It became the longest engagement of my career. I architected the platform, built it, deployed it. I have been its entire engineering department ever since. For seven years, I have handled features, fixes, and late-night server calls. Same client, same codebase, same person answering.
02 / Constraints
One engineer, thousands of companies, no room for lost records
- Regulated recordkeeping. Test results, consent forms, and status changes are compliance artifacts. Losing history isn't a bug; it's a liability for the customer.
- Five kinds of users with real power differences. A super admin and a company team member must never share an inch of accidental surface area.
- Agents work across companies. Some users legitimately operate many fleets. Scoping mistakes here would leak one company's drivers into another's dashboard.
- Sole maintainer. Every clever trick I add, I maintain alone, potentially years later. Cleverness has a carrying cost.
03 / Architecture
Five guards, one schema, no accidental neighbors
Laravel's auth system usually ships with one guard. the platform runs five, and the split is about session models as much as roles: company and super-admin sessions on the web guard, their staff teams on a dedicated team guard, token clients on the API guard. Custom middleware (platform-owner check, subscription check, billing exception check, account status check) layers policy on top.
Agents are the awkward case. Service providers manage testing for many fleets at once, so they authenticate once and switch companies through session-scoped context. From there, queries stay tied to the selected company instead of relying on a developer to remember where company_id = in four hundred places.
04 / State & audit
Status columns grow up into state machines
Employees move through a real lifecycle: pending, approved, active, suspended, declined, restored. Orders move through their own. Early on these were status columns; as compliance requirements hardened, they became explicit state machines with history tables recording every transition: who, when, from what, to what.
Retrofitting explicit states onto years of historical data was one of the platform's more difficult migrations, backfilling history for records that predated the concept of history. It's also why my answer to "when should lifecycles become state machines" is now: before the first production record exists.
05 / Billing
Stripe at three altitudes
The platform charges companies in three ways: recurring subscriptions for platform access, per-order charges for tests and records as they're consumed, and one-time fees like driver enrollment. Setup intents handle card capture; webhooks reconcile all three streams back into a coherent billing state, including the awkward cases where a subscription lapses mid-order.
I built each charge type where it was needed, and the logic spread across controllers. It works, verified by seven years of production billing. A unified billing abstraction from the start would have made every Stripe feature since cheaper. That abstraction is on the migration roadmap.
06 / The living migration
Two frontend stacks, one product, no pause button
The platform started as server-rendered Blade with Vue 2 sprinkled in. That was the right choice in 2019. It is becoming an Inertia and Vue 3 SPA now. The migration is happening one page at a time, with both stacks running in production and sharing auth, sessions, and layout.
Doing this live taught me more about migration strategy than any greenfield project could. Keep the boundary explicit. Migrate whole user journeys rather than random pages. Resist "while I'm here" rewrites. The migration is the work; improvements ride the next train.
- fleet companies served
- active employees managed
- years maintained solo
- authentication guards
07 / Results
The quiet kind of success
3,406 fleet companies. 8,859 actively managed employees. FMCSA Clearinghouse queries, MVR consent flows with e-signatures, state-specific pricing, a built-in support ticket system, shipped and maintained by one person without a major incident across seven years.
This kind of platform does not need a dramatic launch story. The useful measure is simpler: after seven years, the client still sends the next feature request to the same developer because the last three hundred went fine.
08 / Lessons
What seven years of ownership teaches
- Solo longevity is an architecture requirement. Every decision gets judged by "will I understand this in year five?"
- State machines should be explicit from the first record. Retrofitting history is an order of magnitude harder than recording it.
- Scattered billing logic is technical debt with compound interest. Unify the abstraction before the third charge type, not after.
- Long-running systems deserve deliberate documentation. Seven years of context in one head is a bus-factor problem I actively write my way out of.