The Problem
Managing a small fleet of rental vehicles in Sydney with Google Sheets, WhatsApp messages, and manual bank statement reconciliation gets messy fast. Every week, each renter owes a fixed amount. The job: track who owes what, send reminders, collect payments, reconcile records.
The breaking point: reconciling payments late one night and realising we'd double-counted one renter while missing another entirely. Payments weren't just late — they were invisible. Bank transfers showed as random amounts from random names. Matching them was a recurring weekly exercise that still produced errors.
The business cost:
What We Built
We built RentalsCube — a real product we now use to run our own fleet, and offer to other operators with the same problem.
Why Next.js App Router? Speed-to-market. We'd built multiple projects on Next.js — the tooling, deployment, and developer experience were familiar. For a SaaS where shipping quickly matters more than architectural perfection, pick the tool you're most productive in.
Why PostgreSQL over NoSQL? Fleet management data is inherently relational. Vehicles have many rentals. Rentals belong to one customer. Payments belong to one rental. Postgres handles these relationships cleanly with foreign keys, joins, and constraints that prevent the data inconsistency that plagued the spreadsheet.
Why Stripe Payment Links over full embedded checkout? The entire point was eliminating bank transfer chaos. Full checkout with saved cards and recurring billing would have added significant development time. Stripe Payment Links achieved most of the outcome with a fraction of the effort: generate a unique link per renter per week, email it on due day, payment recorded automatically via webhook.
Why Resend for email? Every week on the renter's due day, the system sends an email with their payment link. Unpaid after 48 hours? Automatic reminder. Days overdue? Escalation email to the fleet owner. This replaced the manual WhatsApp chasing ritual entirely.
The Build
Core CRUD. Vehicle management: add, edit, remove vehicles with make, model, registration plate, status. Customer management: renter profiles with contact details. Rental creation: assign vehicle to customer with weekly amount and payment schedule.
No styling beyond Tailwind defaults at first. Just forms that worked. We dogfooded from day one by entering our own fleet data.
Payments. Stripe integration, webhook handling, automated email pipeline. This was the hardest part.
The critical challenge: Stripe webhooks behave differently in development vs. production. We spent real time debugging a webhook that worked in test but failed in production because of a Stripe API version mismatch.
Key architecture decision: handle payment_intent.succeeded idempotently. Stripe retries failed webhook deliveries, so the same payment event might arrive more than once. Without idempotent handling, that's duplicate payment records.
We also built the owner dashboard: fleet size, active rentals, this week's payments, this month's revenue, outstanding balance, overdue alerts — the screen fleet owners actually live on.
Polish and launch. A customer portal for renters to view their rental details and payment history. Overdue reminder emails. Receipt generation. Mobile responsiveness — most renters check email on a phone, so payment emails and Stripe checkout had to work well there.
Migrating Off the Spreadsheet
We migrated our own fleet off Google Sheets onto RentalsCube. Entered all vehicles, customers, and active rentals. Set up weekly payment schedules. Automated emails started going out, and payments began coming in via Stripe link — without a WhatsApp message, bank transfer, or manual reconciliation.
The weekly payment-chasing ritual was over.
What We'd Build Differently
Recurring Stripe subscriptions from day one. Payment links require renters to click every week. Subscriptions with saved cards would automate even further. We avoided this in v1 to ship faster, but it's the most-requested feature.
SMS alongside email. Some renters don't check email reliably. SMS payment reminders would catch more late payments.
Multi-currency support. Not needed for our current market, but early interest from other regions means we may need it eventually.
None of these gaps were worth delaying launch. Every one was identified through actually using the product — which is exactly the point of shipping fast and building for yourself first.