Published · 2026-05-06 · 12 min read
A technical breakdown of motelsystem — the multi-tenant hotel platform I built and ship weekly: how I solved local-government reporting, ran on Cloudflare Workers' 10ms CPU budget, and made it work in EN/AR/KU.
TL;DR: I built a multi-tenant hotel management platform for the Kurdistan Region called motelsystem. It runs on Cloudflare Workers' 10 ms CPU budget, ships in English, Arabic, and Kurdish, prints government-compliant reports for the local registry by one click, and has been live in production through 33 weekly waves. This post is the technical breakdown.
Why a custom hotel platform was even needed
In 2024 a friend in Duhok asked me to look at his motel's "system." It was a 47-tab Excel file, a printer connected by USB, three handwritten notebooks, and a paper folder of regulatory forms half-filled in marker. Check-in took 30 to 45 minutes per guest. Regulatory reports were rejected about once every two weeks for missing fields, and nobody knew which nights were full until a guest showed up.
He'd tried two off-the-shelf hotel SaaS products. Both failed for the same three reasons:
- Neither spoke Kurdish in any meaningful way.
- Neither could output the local-government .docx in the format the office actually accepts.
- Neither handled IQD + USD reliably (or at all).
That third point is interesting because most Kurdistan SMBs operate in two currencies all day. International software doesn't model this; you either pick one currency and lie to yourself, or you keep a parallel paper ledger. Both are bad.
The architecture decisions that mattered
Multi-tenant from day one
I designed the schema for multiple hotels from the first commit. Each property is a tenant; every row in every table carries a tenant_id; Supabase Row-Level Security enforces strict isolation. This was non-negotiable — even if there was only one customer at first, retrofitting tenancy later is a nightmare.
Cloudflare Workers as the runtime
Workers Free has a 10 ms CPU limit per request. That sounds painful for a Next.js App Router app — and it is, until you redesign for it. The rules I now live by:
- Skip middleware auth on anonymous paths. Customer-facing pages don't need to wake up the auth client. Saves ~3 ms per request.
- Use the admin Supabase client for anonymous reads, bypassing RLS for already-public data.
- Pre-fetch in
page.tsxinstead of layering on parallel Suspense boundaries that each spin up a new connection. - Auto-retry from
error.tsx. On a CPU timeout, Workers throws a synchronous error. A simpleuseEffectretry catches the rare miss without the user noticing. - Navigate, don't
router.refresh()after heavy mutations.refreshrebuilds the entire route tree and easily blows the 10 ms budget. A targetedrouter.push(currentPath)after a mutation gets you fresh data without the cost.
EN/AR/KU at the database level
Most "multilingual" apps store English in the primary table and bolt translations on later. I went the other way: every translatable field is a JSON triplet { en, ar, ku } at the column level, with a localizeContent() helper that flattens to whichever locale the request is in. RTL layout is enforced at the layout level — not text-direction CSS — so Arabic and Kurdish UIs are mirror images, not LTR pages with right-aligned text.
The government-reporting problem and how I solved it
The local-government form is a specific .docx file: A4 landscape, 10 RTL columns, yellow header, color-coded data rows (yellow for arrivals, green for current guests, red for incomplete data). The office rejects anything that doesn't match this template.
I reverse-engineered it by inspecting the .docx XML directly, then wrote a generator that takes any night's guest set and outputs a file the regulator accepts on first try. The system tracks every required field per guest (national ID, name, nationality, arrival, departure, room, accompaniment), flags incomplete data with a banner in the dashboard before the export is run, and supports multi-night stays so a guest who arrived three days ago still appears on tonight's report.
The result: zero rejected submissions in 12+ months, and the front-desk operator's reporting time went from "30 minutes plus another 30 if something's wrong" to "press export, fix any flags, done."
Walk-in flow under 90 seconds
Walk-ins are the cash flow of a Kurdistan boutique hotel. They're also where the old system burned the most time. I built a single-page flow: operator selects a room from a real-time availability grid, scans or types the guest ID, the deposit posts immediately to the day's accounting, and a dual receipt prints — guest copy and accounting copy — in under 30 seconds end-to-end. The ID details flow into the night's reporting queue automatically.
Sub-90-second total walk-in handling, including conversation, ID scan, room walk, and key handoff. Operators stop dreading 11 PM walk-ins.
A customer-facing CMS the manager actually uses
Most B2B software fails at the customer-facing layer because the manager can't update content without calling the developer. I built a boutique CMS so the hotel manager can edit hero images, descriptions, amenities, FAQ, contact info, and seasonal offers — in EN/AR/KU — from the same admin he uses for bookings. No "developer required" surfaces. That's the difference between a system that ships and a system that rots.
What 33 weekly production waves taught me
Shipping every week was the single highest-leverage decision. Each wave was small enough to be safe (one or two features, fully tested), but cadence forced compound improvement. Some highlights:
- Wave 12: walk-in flow re-architecture cut handling time by 60%.
- Wave 21: customer-facing CMS launch eliminated 90% of "can you change…" requests to me.
- Wave 27: multi-night reporting tracking caught the missing-data class of bug at the source.
- Wave 33: Workers Free CPU optimization wave — every customer-facing page now hits sub-1.5s LCP across Kurdistan.
Lessons for anyone building software for Kurdistan
- Stop adapting Western SaaS. The cost of building custom is lower than the cost of perpetually fighting a tool that doesn't understand your reality.
- Multi-tenant from day one if there's any chance of a second customer.
- Translation at the column level, not as an after-thought.
- The government format is solvable — it's deterministic, and once you've reverse-engineered it, the second hotel is free.
- Weekly waves beat quarterly releases by a wide margin in this market.
If you have a similar problem
If you run a hotel, motel, clinic, school, or any business in Kurdistan that's been failed by off-the-shelf software, I'm interested. Multi-tenant means I can onboard you on the platform we already have — you don't have to fund building it from scratch. Send a brief on the contact page or message me on WhatsApp.
Related posts
More on the same theme — Kurdistan SMBs, AI, and the messy practical bits.
6 min read
Launching SalonSystem: an all-in-one platform for Kurdistan salons
SalonSystem is live at krd.beauty — bookings, staff, customer history, inventory, WhatsApp reminders, and reporting in one trilingual app, priced for Kurdistan salons. The story behind why I built it and where it's going.
8 min read
The hidden cost of "free" hotel booking widgets in Kurdistan
Why the "free" OTA widget on your hotel site costs a 30-room Kurdistan hotel $18,000–$40,000 a year — what the commission math actually looks like, who owns your guest list, and the owned-direct alternative that pays back in 2–4 months.
9 min read
AI for Kurdistan e-commerce: Shopify, custom, or hybrid?
Eight decision questions to choose between Shopify, custom, and hybrid e-commerce builds in Kurdistan — and where AI fits in each path.