Problem
Lowgolf Travel is a golf travel agency: it sells departures to Scotland, Ireland, Turkey or Portugal where a single group mixes accommodation, tee times, flights, transfers, cars and insurance — each with its own supplier, rate and deadline. That whole operation ran on a CRM assembled from WordPress plugins, on a data model that did not represent what is actually sold (a trip with several travellers, dozens of services and staged payments), with the business information scattered across emails and spreadsheets. The practical consequence: nobody could answer at a glance the only question that matters on a Monday morning — "which departure is going to derail this week?".
Solution
I rewrote the whole CRM from scratch on Laravel 13 (PHP 8.3, Blade + Tailwind v4, Alpine.js, MariaDB, Redis and Reverb for realtime, all in Docker), and migrated the WordPress data with a purpose-built ETL that pseudonymises the personal data of historical records.
The system covers the full sales cycle: a leads inbox with qualification and conversion, opportunities with a funnel, kanban and a working detail view, services with a per-type editor and bilingual supplier availability requests, and versioned quotes the client opens through a public link — no login — and accepts by paying the deposit with Stripe. Accepting is the only thing that crowns a version: from there the payment schedule recalculates itself, the itinerary unlocks and the opportunity moves stage.
The money side is a real administration module: a payment plan computed from business rules, supplier payments on each supplier's terms with credit balances, invoicing with sequential numbering and PDF, budgeted-versus-real margin, and a season cash flow with an exportable daily breakdown. On top sits an operational control tower with gauges and lists of what is still open, an automation engine with triggers and conditions, a Microsoft 365 shared mailbox that attaches emails to their trip, live notifications, roles with fine-grained per-module permissions, 2FA, maintenance mode and a custom-module builder that generates a full CRUD without touching code.
Result
The CRM has been live in production since June 2026 at crmlowgolftravel.com, with Stripe in live mode, transactional mail, encrypted backups twice a day and a pre-production environment refreshed daily from production so nothing ships untested. Behind it: 2,086 Pest tests, a clean PHPStan level 5, Pint and GitHub Actions CI on every push, plus an accessibility audit with no axe violations and no horizontal overflow on mobile.
For the agency the real change is not technical: the entire operation — who owes what, which service is still unconfirmed and which group departs in five days — now fits on a single screen.