Prabhu Thangavel
← Back to blog

How I Built This Platform

The story behind this site — from scattered tools and failed experiments to a zero-cost learning platform that actually works.

How I Built This Platform

This post isn't about the tech stack or the architecture — you can find those in the tech stack post and the course overview. This is the story of why I built it, the problems I ran into, and the decisions that shaped what you see today.


The Problem

For years, my content lived in a dozen different places.

📓
Course Notes

Scattered across Google Docs, Notion, and random markdown files

✍️
Blog Posts

Some on Medium, some in drafts that never got published

📖
Documentation

GitBook for one project, README files for another

🎓
Teaching Material

PowerPoint slides, PDFs, handwritten notes — everywhere

Every time I wanted to share something, I'd have to remember where I put it, update it in one place, forget to update it in another. It was a mess.

I wanted one place — a single repo, a single URL — where everything lives together.


Attempt #1: Stripe Payments (Failed)

When I decided to offer a paid course, my first instinct was Stripe. It's the gold standard, right?

⚠️Reality check

Stripe requires a registered business entity to accept payments in India. As a freelancer without a registered business, I couldn't get past the onboarding.

On top of that, the Stripe SDK crashed my Next.js build because it tried to initialize at module evaluation time — before environment variables were available.

I spent a weekend wiring up Stripe checkout, webhooks, and success pages. Then I realized none of it would work for my situation. Deleted it all.


Attempt #2: Manual UPI (Worked!)

I stepped back and thought about what I actually needed:

  1. Student pays me ₹125
  2. I verify the payment
  3. They get access

That's it. I don't need a payment gateway for that. I need a UPI QR code and a way to verify transactions.

The final solution

The payment page shows a dynamically generated QR code with the amount pre-filled. Student scans, pays, enters their transaction ID, and I get an email. One tap to approve. Done.

No Stripe. No Razorpay. No business registration. Zero payment gateway fees.


The Email Problem

I started with Resend for sending admin notification emails. The emails were technically delivered — I could see them in Gmail search — but they wouldn't render properly. You'd click on the email and see a blank screen.

I tried debugging Resend's delivery for a while before realizing: I'm overcomplicating this. Gmail can send emails to Gmail. Why am I using a third-party service?

Switched to Nodemailer with Gmail SMTP using a Google App Password. Took 10 minutes. Emails land in my inbox instantly, render perfectly, and cost ₹0.


One-Click Approval

The admin dashboard I built works fine, but I'm lazy. I don't want to open a browser, navigate to /admin/payments, and click approve every time someone enrolls.

So I built HMAC-signed approval links directly in the notification emails. When someone requests access or pays for a course, I get an email with a big green button. One tap from my phone → student is enrolled.

💡How the signing works

The approve link contains the user ID, course ID, and a cryptographic signature generated with my Clerk secret key. It can't be forged or replayed — if anyone tampers with the URL parameters, the signature check fails.


No Database, No Problem

One of the best decisions I made was not using a database.

📝
Content

MDX files in the repo — version-controlled, easy to edit

🔐
Access Control

Clerk's publicMetadata — roles, enrollments, pending requests

📊
Progress

localStorage in the browser — per-lesson completion tracking

💰
Payment Records

Email trail + Clerk metadata — enough for my scale

For a single-creator platform, this is more than enough. If I ever need a database, I'll add one. But right now, zero infrastructure means zero maintenance.


Meeting Scheduling

Once the site was live, people started reaching out to connect. I needed a way to let visitors book time without the back-and-forth of "are you free Tuesday?"

Cal.com — free & open-source

I integrated Cal.com for meeting scheduling. It's open-source, connects to Google Calendar, and auto-generates Google Meet links. The embed loads inline on the /meet page — no redirects, no iframes fighting with styling.

The integration is dead simple — the official @calcom/embed-react package provides a <Cal> React component that renders the scheduling widget right on the page. Visitors pick a 15-minute slot, Cal.com handles calendar conflicts, and a Google Meet link is generated automatically.


The ₹0/Month Stack

This was a deliberate goal from the start. Every tool I chose is either free or has a generous free tier.

Hosting

Vercel — free tier

🔑
Auth

Clerk — free tier

📧
Email

Gmail SMTP — free

💳
Payments

UPI — no gateway fees

📅
Scheduling

Cal.com — free tier

📦
Storage

GitHub repo — free


Curated Programs

The generic courses work for most learners, but sometimes people need a tailored curriculum. A DevOps course that covers everything from Kubernetes to Ansible might be overkill for someone who only needs K8s + CI/CD.

So I built a curated programs system — think Spotify playlists for lessons. I pick specific lessons from any course, bundle them into a named program, and enroll the student in just those lessons.

Zero content duplication

Programs don't copy lessons — they just reference existing MDX files by path. A program like "Vedika's DevOps + AI" might pull 2 lessons from DevOps and 7 from AI Bootcamp. Same content, different playlist.

The student sees only their curated lessons with progress tracking scoped to the program. The enrollment flow uses the same HMAC approve links — I just enroll them in a program ID instead of a course ID.

On the learning catalog, programs appear as a single "Curated Programs" umbrella card alongside the courses — clicking it opens a dedicated listing page at /programs, which then links to each individual program. Same folder-style navigation as courses.

To make creating programs easier, I built an admin program builder at /admin/programs. It's a visual wizard — pick an icon, fill in details, browse all available lessons in a tree, check the ones you want, reorder them, and download a JSON file. Drop the file in content/programs/ and push. No TypeScript editing required.


Lessons Learned

🎯What I'd tell past me
  • Start simple — I wasted time on Stripe when a QR code was all I needed
  • Use what you already have — Gmail sends emails, Clerk stores data, localStorage tracks progress
  • Don't build for scale you don't have — a database is overhead when Clerk metadata does the job
  • Signed URLs are underrated — HMAC links let me handle admin actions from email without logging in
  • MDX is powerful but quirky — complex JS in props breaks the parser, use children-based patterns instead
  • Embed, don't rebuild — Cal.com handles scheduling better than anything I'd build myself

What's Next

  • More courses and lessons
  • Certificate generation on course completion
  • Community discussion threads
  • Mobile-optimized reading experience

If you want the technical details, check out the tech stack and course overview posts. And if you're building something similar — reach out, happy to help.