Multi-tenant SaaS · live
The daily work of a hotel or facility team, on one board and in their WhatsApp.
Managers plan tasks, recurring rounds and automation rules. Staff check in with a QR code at the door, attach photo evidence and can work from plain WhatsApp messages. Every company is its own tenant, isolated by design.
The short version
What it is, in plain words
A hotel has rooms to clean, a pool to check every morning and a boiler to inspect every first Monday. Checklee turns that into tasks on a kanban board, generates the repeating ones on their own, tells the right person when something is due or late, and records who did what, when and with which photo.
Counted from the repository, October 2026.
Things you would not see from the login page
Under the hood
One line each. Open any card for how it actually works.
A rule engine, not a settings page
"When a room turns dirty, create a cleaning task for housekeeping and notify the shift lead."
Automations are trigger → conditions → actions. Six trigger types (unit or task status change, task completed, field change, manual button, time-based), 14 condition operators including in, is_empty and regex, combined with all/any.
Nine action types: create a task or a linked task, change a task or unit status, notify a role, team, department or person, comment, archive, wait, email a report. Texts take {{field.path}} variables. Every run writes a per-action execution log, and a dry-run mode tests a rule before it goes live.
Recurring tasks that understand calendars
"Every last Friday of the month at 09:00, Istanbul time, ten times."
A master task generates its own instances: daily, chosen weekdays, monthly, or the Nth (or last) weekday of the month. Each master carries its own IANA time zone, generation time, due-time offset and an end condition (a date or N occurrences). It can drop instances into a chosen board column and be paused and resumed.
Undo, with a paper trail
Changes are recorded with before and after, and a recent edit can be rolled back.
The audit log stores actor, target, old and new JSON, IP, user agent and session. Platform administrators can roll back an update made in the last 24 hours, restoring the old values, for nine resource types: company, site, department, team, task, unit, role, template and user.
WhatsApp as a second interface
Staff type "geldim" and they are checked in. In Arabic or Ukrainian, too.
Inbound messages from the Meta Cloud API go through a command parser that understands check-in and check-out, breaks, task lists and SOS, with keywords in Turkish, English, Arabic, Russian and Ukrainian. A shared location is matched to the nearest site with a Haversine geofence. Each company or tenant can bring its own WhatsApp number, with a platform number as the fallback.
Signed QR codes on every door
A printed code that cannot be forged for another room.
Each code carries company, site and unit, signed with HMAC-SHA256. Generating a new code retires the old one, every scan is counted, and a whole building's codes are generated in one go.
A tracking link for the end customer
Share one task's progress with a guest or client, no account needed.
Public links expire after seven days by default, and seven switches decide what the visitor sees: progress, photos, notes, checklist, media, assignee, comments. Public comments are rate-limited.
Task dependencies, project-tool style
A task cannot start while the one blocking it is open.
Four dependency types (finish-to-start, finish-to-finish, start-to-start, start-to-finish) and nine link types (blocks, blocked by, related, causes, caused by, duplicates, duplicated by, parent, child). The resolver walks the dependency graph and checks blockers before a task can move forward.
Plans enforced in the code path
Four subscription plans, about 20 limits each, checked where things are created.
Users, companies, sites, units, boards, tasks, templates, automations and CRM boards all pass a limit check on create, in 11 create paths. Monthly usage counters reset on a timer, trials expire on their own and cancellations step a tenant down to the free plan.
Knows what each tenant costs
Usage is attributed to tenants, so pricing can follow real cost.
A middleware buffers request activity in memory and flushes it every five seconds, off the request path. A nightly timer turns that into per-tenant cost attribution, while hourly and daily roll-ups feed the dashboards.
For engineers
Architecture
Fully serverless on Azure. Three ways in, one pipeline every HTTP request goes through, a set of domain engines, and storage partitioned by tenant.
Drawn for this page from the codebase. No customer data or screenshots.
1 · Tenant isolation through the partition key
Every company is a tenant, and most containers are partitioned by /tenantId. The tenant is read from a claim in the signed JWT; if a request also carries a workspace header that disagrees, the token wins and the mismatch is logged. Repositories take that tenant as the partition key, so a normal read or write is scoped to one tenant's partition and stays cheap, and reporting across tenants goes through deliberate fan-out queries and separate roll-up containers.
2 · Serverless, and paid for by the invocation
The API is 540+ isolated-worker functions on a consumption plan over Cosmos DB serverless, so an idle tenant costs close to nothing. That makes timers the real bill: 21 of them run reminders, overdue alerts, recurring task generation, scheduled automations, shift reminders, analytics roll-ups, trial expiry and account deletion with a 14-day grace period. Moving the most frequent ones from one minute to five, and presence cleanup from five minutes to thirty, cut scheduled timer runs from 3,728 to 1,184 a day. Adaptive sampling keeps telemetry small while every exception is still recorded.
3 · Sessions that survive, and notice theft
Access tokens live an hour; refresh tokens are 64 random bytes and live 30 days. Each refresh revokes the old token and links it to its replacement. If a revoked token is ever presented again, every token of that user is revoked, because it means someone else had a copy. In the browser, parallel requests that hit a 401 share one refresh instead of racing each other.
4 · Two layers of roles
Platform roles (five, mapped to 15 permissions such as tenant management and impersonation) run the SaaS itself. Inside a tenant there are owner, admin, member and viewer, plus custom roles with their own permission lists that can be scoped to one company.
5 · Errors you can trace
Every request gets a correlation ID. Typed domain exceptions map to 400, 403, 404, 409 or 422 with one error envelope, and are sent to Application Insights tagged with tenant and user. Request-body logging can be switched on at run time, with passwords and tokens masked before anything is written. A maintenance switch lets only administrators through.
Design decisions
What was chosen, and what it costs
| Decision | Why | The price |
|---|---|---|
| Functions on a consumption plan | Cost follows usage; a SaaS with a handful of tenants runs for under ten dollars a month. | Cold starts, and every timer is a line on the bill, so schedules have to be tuned. |
| Cosmos DB, partition per tenant | Isolation and scaling come from the same key; queries inside a tenant stay cheap. | Cross-tenant reporting needs deliberate fan-out queries and its own roll-up containers. |
| WhatsApp as a first-class channel | Field staff already live there; no app to install, no password to forget. | Free text is ambiguous, so commands need a parser and keywords per language. |
| Plan limits checked in the create path | A limit cannot be bypassed by a client that skips the UI. | Every new resource type needs its limit wired in by hand. |
| Hand-written auth checks per function | Each endpoint states exactly which role it needs, next to the code it protects. | More code per endpoint, and consistency rests on review rather than on a framework. |
Built with
Stack
Backend: .NET 8 isolated Azure Functions · Cosmos DB serverless · Azure Blob Storage · Azure Communication Services (email) · WebPush (VAPID) · Meta WhatsApp Cloud API · Application Insights · xUnit, FluentAssertions, Moq.
Frontend: React 18 · TypeScript · Vite · Tailwind · Radix UI · TanStack Query, Table and Virtual · TipTap · Leaflet · a home-grown i18n layer with right-to-left Arabic · PWA with a custom service worker.