LetWise UK
A landlord wealth-management platform — mortgage, tenancy, and cashflow tracking with an automatic remortgage-savings finder, plus a role-gated admin console. Built for a friend managing a multi-property portfolio.
A friend of mine manages a portfolio of rental properties in the UK, and was tracking mortgages, tenancies, and rental income the way most landlords do — spreadsheets, plus whatever each mortgage lender's portal shows in isolation. The two things that actually matter for a landlord's return — whether any mortgage's fixed-rate period is ending soon, and whether a better rate is now available elsewhere — weren't connected anywhere. Compliance deadlines (EPC ratings, gas safety certificates) added another set of dates to track by hand.
LetWise UK was built to put portfolio equity, cashflow, and compliance deadlines in one dashboard, and — the more novel part — to automatically flag when a property's current mortgage rate is worse than what's newly available, without the landlord having to go shopping for rates themselves.
My friend, a portfolio landlord managing multiple UK rental properties, is the primary user — tracking property valuations, mortgages, tenancies, and cashflow in one place. A second, role-gated admin interface is for platform operations: managing the market-rate data that powers the savings comparisons, monitoring subscriptions, and handling remortgage referrals.
Solo engineering team, alongside a full-time product role at TCS — product design, data model, and full-stack build (TanStack Start frontend, Supabase schema and RLS policies, Cloudflare Workers deployment, Paddle billing integration).
LetWise UK is a single TanStack Start application — React 19 with file-based routing, server-rendered and deployed to Cloudflare Workers — rather than a separate frontend and API. Role-gated route groups (/_authenticated/* for the landlord app, /_authenticated/_admin/* for the admin console) mean one deployment serves two distinct interfaces without a second app to build or ship.
Access control lives in the database, not the app. Every table — properties, mortgages, tenancies, transactions, subscriptions — has Row-Level Security enabled in Postgres via Supabase. Rather than each policy re-deriving "is this user an admin," a single has_role() security-definer function is called from every policy that needs it (see the snippet below). Landlords see only rows where auth.uid() = user_id; admins pass a role check instead. The authorization logic exists in exactly one place, not duplicated across a dozen policies or re-implemented in application code.
The savings finder is a straight comparison, not a black box. Each mortgage is matched against the latest market_rates row for its LTV band and term type, and the difference against the mortgage's current rate becomes a "potential monthly saving" shown on the landlord's dashboard. Acting on it logs a row to referrals, which shows up in the admin console's referral queue — the whole feature is a join and a subtraction, not a model.
Billing is Paddle, wired through Supabase Edge Functions. Checkout runs client-side via Paddle.js; a Supabase Edge Function verifies and handles the webhook, updating the subscriptions table server-side so subscription status is never trusted from the client.
Admin is a first-class interface, not an afterthought. The admin console (visually distinct — emerald rather than the landlord app's indigo) covers rate management, user administration, a broadcast tool for announcements, the referral queue, and a read-only audit log that every admin write goes through.
-- one security-definer function, called from every RLS policy that needs a role check
create or replace function public.has_role(_user_id uuid, _role public.app_role)
returns boolean language sql stable security definer set search_path = public as $$
select exists (select 1 from public.user_roles where user_id = _user_id and role = _role)
$$;
-- used directly in policies, e.g. (properties table):
create policy "properties_admin_select" on public.properties
for select using (public.has_role(auth.uid(), 'admin'));
In active use by the friend it was built for, tracking their property portfolio day to day — mortgages, tenancies, cashflow, and compliance deadlines in one dashboard instead of scattered across lender portals and spreadsheets.
The architecture decisions travel well beyond a single user, though: role-gated routing in one deployment, RLS-enforced multi-tenancy through a single has_role() function, and Paddle billing wired through server-verified webhooks are the same shape a genuine multi-landlord SaaS product would need — nothing here was built assuming there'd only ever be one user.