Skip to content
Book a Free Call

Case Studies

How I work.

Situations from behavioral health and wellness practices I've built for, plus a few pieces of my own infrastructure, described the way I'd describe them after a first call.

Some of these I built and handed over. Some I still run. A few I built for myself and still run today. The difference is who owns it afterward, and for three of these, that's me.

Every referral in one shared sheet

Built for you: referral and intake trackers

The situation

You've got one person standing in the middle of every referral: assigning it to a clinician, emailing over the details, then chasing updates just to know whether it converted. Every referral needs them, and that stops scaling the moment your team grows.

What I built

I build a tracker for each clinician that syncs both ways with your master sheet. A referral you assign lands in that clinician's own tracker automatically. Whatever they update syncs back without anyone re-typing or relaying it by hand.

Two-way sync
Your master sheet Single source of truth. Every referral, every status.
Each clinician's own tracker Their caseload only. Nobody else's.

What became true

Each clinician sees only their own caseload. Nothing gets re-typed or relayed by hand. Your master sheet stays the single source of truth, and whoever's coordinating referrals is no longer the bottleneck every one of them has to pass through. This isn't a one-off: I've since built this same two-way tracker for another practice with the same problem.

Payroll rebuilt by hand, every pay period

Run for you: Payroll

The situation

Variable clinician pay means every pay period starts from scratch: pull the billing, reconcile it against what actually came in, work out each clinician's split, then rebuild the payroll entry by hand. Every step is a place to introduce an error, and errors in someone's paycheck are not a small problem.

What I built

Your clinicians submit their own billing extras from their own sheet. Those flow into a reconciliation against the actual transactions, then into a per-clinician earnings summary and a payroll-ready export. Failed submissions get logged and can be replayed instead of lost.

The pay run
Billing extras Submitted by each clinician.
Reconciliation Checked against what actually came in.
Earnings summary One per clinician.
Payroll export Ready to file.

What became true

Your pay run becomes a review step instead of a rebuild. Each clinician gets their own summary. Nobody needs access to the payroll sheet to submit what they're owed.

No clear read on clinician performance

Built for you: dashboards and reports

The situation

You can see revenue. You can see sessions. But you can't answer a simpler question: which clinicians are performing well by the metrics that actually matter to you as the owner, and which need support. The data exists. It just never gets assembled into an answer.

What I built

I build a per-clinician scorecard from your attendance and referral data: conversion, retention, churn, and average sessions per client, the metrics that show how well each clinician is helping the people in their care.

The scorecard
Attendance and referral data Already collected. Never assembled.
Shared measurement window So clinicians are compared fairly.
Per-clinician scorecard Conversion, retention, churn, sessions per client.

What became true

Your question has an answer that refreshes on its own. Patterns in performance show up while there's still time to step in and help, rather than at year end.

A tracker that outgrew what a spreadsheet should do

Co-founded: The Better Mind Center's team portal

The situation

The Better Mind Center, the group therapy practice I co-founded, had outgrown what its own referral tracker should be asked to do once the whole team needed to work from it at once, not just one person checking a shared sheet.

What I built

I put a proper web app in front of the existing tracker: a React and Express application on Cloud Run, with the same Google Sheet still underneath as the store. That's the decision people push back on most. Keeping the existing data layer meant the practice never had to stop working during the change, and staff who preferred the sheet could keep using it.

One data layer, two front doors
The practice's spreadsheet Still the source of truth. Nothing migrated.
Team portal (Cloud Run) React and Express, built for the whole team.

What became true

The whole team now works from one shared, real-time view instead of one person's inbox, and nothing about the migration required the practice to pause or retrain around a new source of truth.

A practice site migrated off Squarespace, rebuilt in the open

Co-founded: The Better Mind Center's website

The situation

The Better Mind Center, the group therapy practice I co-founded, was running its site on Squarespace: workable early on, but increasingly boxed in by the platform's own constraints as the practice grew.

What I built

I migrated the site onto Cloudflare and rebuilt it with a hand-tuned, scroll-driven parallax system across the hero, approach, team, and footer sections, no page-builder templating.

What became true

The site now runs on infrastructure built the same way the rest of my work is: no page-builder lock-in, and room to keep evolving as the practice does.

A form that has to assume every submission might be a bot

My own infrastructure: the contact form

The situation

You're on this site right now, and if you use the contact form below, your submission runs through a gate before it reaches me. That gate exists because public forms get hit by bots and scrapers constantly, and the naive fix, running every submission through paid bot-detection, would work but would also spend money and time on requests that were never going to reach a human anyway.

What I built

I built an Apps Script web app that checks the cheapest signals first. It validates the request type against an allowlist before spending anything on a bot assessment, then runs a honeypot check, then reCAPTCHA last. Order matters: an unknown request type should cost nothing to reject. One request type, unsubscribing, is permanently exempt from bot enforcement, because wrongly blocking an opt-out is a legal problem, not just an inconvenience.

Cheapest check first
Request type Checked against an allowlist first.
Honeypot check A hidden field a bot fills and a human never sees.
reCAPTCHA Runs last, only after the cheaper checks pass.

What became true

Real submissions pass through in under a second and never notice the gate. Bots and scrapers get rejected at the cheapest possible check instead of burning a paid API call, and an unsubscribe request can never be accidentally blocked, no matter how the bot-detection layer behaves.

Sending a newsletter without a mailing list vendor

My own infrastructure: the newsletter

The situation

If you sign up for my newsletter, there's no Mailchimp or ConvertKit behind it. Most newsletter tools charge by subscriber count and hold your list on their own infrastructure, a reasonable trade for most people, but not one I wanted for a list this size, running through the same infrastructure as everything else I build.

What I built

Signup, welcome email, batch sending, and opt-out, all running on Apps Script against the same spreadsheet the contact form already writes to. No email platform, no per-subscriber pricing, and no third party holding the list.

Signup to send
Signup The same spreadsheet the contact form writes to.
Welcome email Sent automatically on signup.
Batch send / opt-out Apps Script limits set the real ceiling.

What became true

You sign up and get a welcome email automatically, sends go out in batches against Apps Script's own limits, and opting out removes the row with nothing left behind. The constraint I accepted going in: those sending limits are real, so this works at the scale I'm actually at and would need replacing well before it became a bottleneck.

The page you're reading right now

My own infrastructure: this website

The situation

You're looking at it: this site is a personal build log, not a template pulled from a page builder, and every page on it is meant to still make sense to edit a year from now without untangling a framework version bump first.

What I built

Hand-written HTML and CSS, deployed to Vercel. No framework, no build step, no dependency to upgrade. The one genuinely complicated part was the scroll behavior in the hero, and it took several passes to stop it drifting on mobile.

What became true

A static site is an unfashionable answer, and it has needed almost no maintenance since. Nothing here breaks because a package updated somewhere upstream.

Every one of these started as a conversation about something that was quietly costing time.

Book a free discovery call

Or see how these were built →