Skip to content

← Work

Roostly

Case studySaaS

Roostly
  • Roostly — view 2
  • Roostly — view 3
  • Roostly — view 4

Direct-booking sites for apartments and small hotels — calendars synced with Booking and Airbnb, so the commission stays with the owner.

A design-first booking platform that lets apartment operators sell direct — and stop handing 15–18% of every reservation to Booking.com.

Most of my work is for clients. Roostly is mine. I'd kept running into the same thesis on rental projects: the tools these operators use are expensive, ugly, and foreign. So I validated it, then designed and built the whole thing solo — product, booking engine, channel sync, the Polish payment and invoicing layer, the security pass on top. This is the case study for how it came together and the decisions that mattered.

The problem is arithmetic

A short-term rental operator with 40,000 PLN a month in bookings pays Booking.com and Airbnb somewhere between 15% and 18% on every reservation. That's roughly 6,000 PLN a month. Over a year, about 72,000 PLN gone — not to marketing, not to product, just to being listed.

Every operator knows a direct booking is worth more than an OTA booking. The reason they don't chase direct is that the alternatives are bad. The property-management tools that come with a website builder — Lodgify, Smoobu, OwnerRez — ship templates that looked dated in 2015. Read their reviews on G2 or Capterra and "the design is poor" is the consensus, not the exception. So operators run to Wix or Webflow to get something that looks like their brand, and the moment they do, they lose calendar sync. No sync means double bookings. A double booking means a cancelled guest, a refund, and a dented reputation.

For Polish operators there's a second gap on top of the ugly one. The foreign tools don't take BLIK or Przelewy24, don't issue KSeF invoices (mandatory in Poland from April 2026), and put the owner in front of an English-only panel. So the honest choice was: pretty site with no engine, or working engine that looks like everyone else's and can't take a Polish payment.

Roostly is the third option.

The thesis: don't rebuild the channel manager

The obvious way to build this is also the way that kills it. To sync availability with Booking.com and Airbnb in real time, you need partner access to their APIs. Airbnb's is closed to a startup with zero listings. Booking.com's takes three to six months of certification before you can send a single date range. Spend that time and you've built plumbing, not a product.

So I didn't build the channel manager. I rented one. Channex.io is a white-label connectivity layer. It holds the certified pipes to Booking, Airbnb, Vrbo and the rest, and exposes them over a clean API. Roostly sits on top of it.

The important decision wasn't using Channex — it was refusing to depend on it. Roostly's own database is the source of truth for every reservation and every open date. Channex is a bridge, nothing more: a booking comes in through it, lands in my database; a date closes in my database, gets pushed back out through it. If Channex ever has to go, the product doesn't go with it. That's the difference between renting infrastructure and being owned by it.

What I designed

The whole pitch collapses if the generated site looks like Lodgify, so design was the product, not the paint.

Instead of one template with a color picker, Roostly ships four visual directions (Editorial, Minimal, Warm, Coastal), each a real React theme, not a skin. A boutique villa in Zakopane and a block of city apartments shouldn't wear the same layout, and they don't. Under the hood the themes are variants driven by CSS custom properties, so a client's colors and type flow through without forking the codebase. No Tailwind — SCSS Modules throughout, because the styling had to stay legible and ownable, not a wall of utility classes.

Two things on the marketing site do more selling than any headline. The first is a savings calculator: an operator types in their monthly revenue and watches the yearly commission they're feeding the portals appear on screen. It isn't a gimmick. It's their own number, and it's usually bigger than they'd admit out loud. The second is a two-minute brief generator that lets a prospect sketch what they want before any call, no login. Both exist to make the value obvious before I have to explain it.

What I built

The product is a multi-tenant SaaS. One database, with every table scoped by organization, so one operator can never see another's bookings, guests, or revenue — tenant isolation enforced at the query layer, not hoped for at the app layer. Auth runs on Clerk, so session and revocation are handled by people whose full-time job is handling them, and I'm not maintaining a home-grown token store I'd get wrong.

The parts that had to work:

Channel sync. Bookings and availability move both ways through Channex over webhooks, processed as background jobs on Inngest, with my database as the record of truth. Polish channels that Channex doesn't cover, like Nocowanie.pl and Travelist, sit on a separate iCal path, because covering them is exactly the wedge a foreign PMS won't bother with.

Payments that a Polish guest will actually use. Stripe for cards and international, Przelewy24 for BLIK — the payment method most Poles reach for first. Stripe has carried Przelewy24 with BLIK natively since 2024, which meant one integration instead of two.

Compliance as a feature, not an afterthought. KSeF invoicing is built in because from April 2026 it's the law, and a Polish operator whose tool can't issue a compliant invoice isn't a customer.

Not double-booking anyone — ever. This is the part that has to be perfect, so it got the most care.

The hard part: the race no one sees until it's 2 a.m.

Two guests, two devices, the same apartment, the same week, the same 30 seconds. Meanwhile a Channex webhook is trying to close that same week because someone just booked it on Booking.com. Get this wrong and the failure doesn't show up in a demo — it shows up as a furious phone call and a refund, and it destroys the one thing the product is selling: that direct is safe.

So a booking attempt takes a row-level lock on the date slot before it does anything else, and the guest's payment intent holds that slot for 30 seconds — enough time to pay, not enough to squat. A competing attempt in that window waits or fails cleanly instead of racing. The Channex webhook that changes availability mid-flight has to be reconciled against the held slot rather than blindly applied, which means retries and rollbacks are part of the design, not an edge case bolted on later. Boring when it works. That's the goal.

Security wasn't a phase

I ran a full security audit on Roostly and wrote it up like I would for a client, because "it's my side project" is not a threat model.

What it found: no SQL injection, because Drizzle parameterizes every query and there's no string-built SQL anywhere. Tenant isolation is consistent across the codebase, so there's no path to read another organization's data by guessing an ID. Webhooks verify their signatures and fail closed — a forged or unsigned call is rejected, not trusted. The iCal importer has an SSRF guard, so a malicious calendar URL can't turn the server into a proxy into my own network. Secrets stay out of git. Security headers and a content-security policy are in place. The genuinely dangerous surfaces on a booking product (payments, availability, tenant boundaries) got audited first, because those are where a mistake costs real money.

The point of putting this in a portfolio isn't the checklist. It's that I treated my own product with the same paranoia I'd bring to yours.

Where it stands

Roostly is built and deployed. The engine works, the sync works, the payment and invoicing layer works, and the marketing site is live at roostly.pl. It's in market entry — I'm talking to the operators it's for, the ones running 5 to 30 units who currently take reservations by phone and hand 15–18% of their revenue to a portal.

I'm not going to dress that up with invented numbers. There's no "+38% direct bookings" here yet, because the honest version is that the product is finished and the customers are early. What this case study is actually evidence of is narrower and, I'd argue, more useful: that I can take a market thesis, pressure-test it, design something that doesn't look generated, build the full stack behind it (multi-tenant, payment-grade, compliant), and harden it, on my own.

Stack

Next.js 16 (App Router) · Drizzle ORM · Neon Postgres · Clerk (auth) · Channex.io (OTA connectivity) · Stripe + Przelewy24 / BLIK · KSeF (invoicing) · Inngest (jobs & sync) · Resend (email) · Vercel · SCSS Modules

Roostly — roostly.pl

Roostly — view 5
Roostly — view 6
Roostly — view 7