Four years of "we'll build it later," one whiteboard session, and the engineering decisions that came out of it.
We started with a whiteboard and one rule. Nobody says "it depends."
Three of us in the room. Me, and the two software engineers who together make up the core of TuSpire Technologies. One question written across the top of the board: what actually happens between a parent's first WhatsApp message and a student's fifth class?
By then Tutify had been running for four years. More than a thousand students. Over ten thousand hours of one-on-one IGCSE, O-Level and A-Level classes, delivered to families in the UAE, Saudi Arabia, Qatar and Oman. Nobody in that room was new to the business.
It took us most of a day to answer the question, because we couldn't agree on it.
That day, not the software, is what this article is about.
The disagreement
We didn't have one process. We had three, running quietly in parallel for years, and none of us had noticed.
Ask whoever handles leads when a student becomes a student. The answer is: when the parent confirms after the demo. Ask whoever handles scheduling, and it's when the first paid class lands on the calendar. Ask whoever handles payments, and it's when the money arrives. Those are three different moments. Sometimes a week apart. Every report we'd ever produced was quietly mixing them together, and every conversion rate we'd ever quoted to ourselves was therefore slightly fictional.
This is what four years of spreadsheets buys you. Not chaos. The classes always happened, the teachers always showed up, the students passed their exams. What you get is ambiguity converted into columns. Every operational problem produced a new field called status or notes, and everybody moved on. The sheet grew. The decision never got made. At some point the spreadsheet stopped being a tool and became the place where decisions went to hide.
Software won't let you do that. You can't write a schema for a concept the business hasn't defined. One of the engineers asked what the enum for student state should be, and the room went quiet, and then we argued for two hours about what a student is at Tutify.
Out of that session came the questions that shaped everything after it. When exactly does a lead become a student? What separates a completed demo from a converted one? Who owns follow-up after a trial class, by name, not by department? Which statuses are useful and which just create noise? What should the admin team see in the first three seconds of opening the portal? And which fields do we genuinely need, versus which do we collect because we've always collected them?
None of that is a technical question. All of it had to be settled before a single table could exist.
We thought we were starting a development project. We'd actually started an operational audit, and the software was the thing it produced on the way out.
Buy or build
The obvious move was to buy a school management system. We looked for about a week and stopped.
Everything in that category assumes fixed classrooms, fixed batches, fixed periods, one timetable, thirty students in a room. Tutify is one-on-one and online. Our primitives were never student, teacher, class. They're demo quality, teacher-to-subject-to-board fit, delivered hours, payment state, who owns the follow-up, and whether the student is genuinely improving or just attending. Any off-the-shelf system would have stored our records perfectly while understanding none of the decisions sitting behind them.
Which is the real test, and it has nothing to do with licence cost. If your process is standard, buy. If your process is the product, build.
The stack, and why
Next.js on the App Router, TypeScript, Tailwind, Supabase, deployed to Vercel.
Boring, and boring on purpose. We had three engineers, an academy that had to keep running through the whole build, and no appetite for a system that only one of us could maintain six months later.
The decision that mattered was Supabase, and specifically Postgres row-level security.
In a tutoring academy, access control isn't a nice-to-have you bolt on before launch. A teacher sees their own students and nobody else's. An admin sees everything. A parent opening a token link sees exactly one child's schedule, and nothing else exists for them. We could have enforced all of that in application code and hoped nobody ever wrote a careless query. Instead we pushed it down into the database, where a forgotten WHERE clause can't leak another family's data because the database itself refuses.
The rule we ended up with: if your authorization lives in a React component, you don't have authorization. You have a suggestion.
The rest of the stack followed from wanting to ship rather than wanting to be interesting.
What broke
Two stories. Nobody puts these in a portfolio, which is exactly why they're worth telling.
The thousand-row ceiling
Delivered class hours are the number the entire academy runs on. Billing, teacher payouts, what we tell parents. Our totals were wrong. Not dramatically. Just always a little low, in a way that looked completely plausible.
The cause was a default row limit in Supabase's API layer. Queries returned the first thousand rows and stopped. No error. No warning. Nothing in the logs. The code was correct and the data was correct, and the boundary between them was silently truncating. Every aggregate built on top of it was a lie, and it was precisely the kind of lie you'd never think to check.
If you've ever wondered why an experienced engineer paginates a query that obviously fits in one page, this is why. It fit last year.
Cron on a platform with no cron
We needed scheduled jobs. Follow-up reminders, session-cycle rollovers, nightly reconciliation. Vercel's free tier had no scheduler.
The lazy answer was to upgrade the plan. The right answer, for six scheduled tasks, was to run them as GitHub Actions on a cron schedule hitting authenticated endpoints. Free, versioned, logged, and reviewable in the same pull request as the code they call. It's been running that way ever since.
Neither of those is glamorous. Both are roughly the entire distance between a demo and a system that's still standing a year later.
What we shipped
The first release covered the student lifecycle end to end. Lead capture, demo, conversion, teacher assignment, scheduling, delivered-hours tracking, payment state. The follow-up ownership rules we'd fought about on the whiteboard went into the schema, not into anyone's memory.
Then it kept growing. A parent-teacher meeting module with a fifteen-status pipeline, bulk scheduling windows, public token links so a parent can book a slot without ever making an account, and separate feedback flows for the teacher and the parent. An approvals and analytics dashboard. Teacher notes attached to the student record instead of buried in a chat thread from March.
We tested hard on anything where being wrong is expensive: hours, money, access. Everything else we shipped and then fixed. A few screens got rewritten after two weeks of real use. Fields we'd argued over turned out to be irrelevant. Things we'd dismissed became load-bearing. That isn't a planning failure, it's what planning is for. You get close enough that reality can correct you cheaply.
What actually changed
Not speed. Hesitation.
Here's the thing about a spreadsheet operation. Even when the answer exists, somebody has to go find it, confirm it, and usually check it against a second person who remembers it differently. Nobody bills that time. Nobody sees it on any report. It just makes everyone slightly slower and considerably less certain than they should be.
A parent asks whether her son's chemistry hours were increased. Before, answering that meant two WhatsApp group threads and a spreadsheet. Now it's one record, with the change, the date, and who confirmed it. The answer takes seconds and, more importantly, nobody has to trust their memory to give it.
The other thing that changed is that problems got harder to ignore. A student with no follow-up in eleven days is now visible on a dashboard instead of being nobody's fault. Some weeks I've missed the ambiguity.
This matters more in education than in most sectors, because parents aren't really buying classes. They're buying confidence. A missed follow-up is an admin issue internally. To a parent, it reads as carelessness about their child. A schedule change communicated late is minor to the team and not even slightly minor to a student sitting an exam in two weeks. Operations aren't adjacent to the student experience. They are the student experience.
The part I'd rather not write
I knew we needed this in year two.
I could have described the entire system back then. Student profiles, demo pipeline, teacher assignment, hours, payments. It sat on a list for two more years while we added columns.
It wasn't time and it wasn't budget. Building meant deciding, and deciding meant sitting in a room and discovering that after four years of running this business, three of us would define its most basic concept three different ways. A spreadsheet lets you avoid that conversation indefinitely. You add a column and move on. Software makes you say it out loud, in front of people, and then live with it.
The engineering took five months.
The thing we'd actually been afraid of took one whiteboard and a two-hour argument.
So if you're sitting where I was, knowing exactly what needs building and finding reasons not to start, I'd bet the obstacle isn't the code. It's the meeting.
Mashhood is the founder of Tutify, a one-on-one online academy for British curriculum students across the GCC, and of TuSpire Technologies, which builds custom internal systems for businesses that have outgrown their spreadsheets.
Top comments (0)