Building Client Portals from Scratch: Magic Links, Billing, and That Dark Mission-Control Vibe
Just wrapped up a massive coding session building out the entire Client Portal system for AdminStack. This was one of those sessions where you start with a simple idea and end up building way more than you planned – in the best way possible.
What Got Built
Started by cleaning up some MemStack documentation and fixing a pesky table name bug (organization_members vs team_member_organizations – always fun when those slip through). But the real meat was building the complete Client Portal feature from the ground up.
The portal system has everything you'd want: a dashboard for managing portals, public portal pages that clients can access without any login hassle, file uploads, messaging, and progress tracking with those satisfying milestone sliders.
The magic is in the access system – clients get a 64-character hex token that gives them direct access to their portal. No passwords, no account creation, just click the link in the email and you're in. Used SendGrid to fire off those magic-link emails automatically when portals get created.
The Billing Integration
Then I got carried away (as usual) and built the entire billing system too. Portal invoices with auto-generated numbers (INV-YYYY-NNN format), Stripe Payment Links that create persistent "Pay Now" buttons, and webhook handling for when payments come through.
The cool part is the overdue detection – it runs both in real-time when you view invoices and via a background cron job to keep the database updated. There's something satisfying about building systems that just work automatically.
Technical Decisions
Went with a dual route structure: /api/portals/ for authenticated admin stuff and /api/portal/ for public token-based access. Keeps things clean and makes the security boundaries super clear.
For the public portal, I built it outside the main dashboard layout with a dark mission-control theme. Gives it that professional, focused feel that clients will appreciate.
Stripe Payment Links instead of Checkout Sessions was a key choice – persistent URLs mean invoices can be paid multiple times if needed, and the links don't expire.
The Numbers
26 files changed, +2,686 lines of code. Three new database tables, 8 API routes, complete UI flows for both admin and client sides. All committed and pushed across 6 commits.
Still need to run the migrations on Supabase and set up that daily cron for overdue detection, but the core feature is complete and ready to ship.
Days like this remind me why I love building – starting with a simple idea and ending up with a full-featured system that just works. Client portals are now live in AdminStack, and I'm pretty excited to see how teams use them.