A job marketplace with four production surfaces: iOS and Android, the API, a web portal, an admin panel. I built all of it, and I still run it.
Every row above is mine.
Putora is a job marketplace for people who work while they travel. They find roles, companies find them, and the two talk inside the app. It ships as an iOS and Android app carrying both sides, a web portal, and an admin surface behind it. The product is the client’s. This page is about the engineering.

Screenshots are from a demo build. Every name, face, company, listing and message in them is seed data. No real account appears on this page.
Three clients, one backend, a load balancer in front. The app tier answers the REST calls and holds the sockets. Managed Postgres keeps the data, object storage keeps uploads and the built web bundles. Billing and email are bought, not written.
Two kinds of user in one native app, plus the portal, the admin panel and the API all three share. With no team to negotiate with, the only thing holding them together is convention: one HTTP client, one state pattern, one deploy path. Add a field to a response and it reaches every surface the same afternoon.
There were paying subscribers on both sides of the cut, so the swap had to happen on a live revenue path with the old and the new provider both authoritative for a while. Entitlements resolved through the whole migration. Nobody lost access to a subscription they had already paid for, which is the only part of this a user is ever meant to see.

A few screens are shared with the web surface instead of being built a second time in Flutter, so there is one implementation and nothing to drift. The seam between the two runtimes is a contract like any other: defined on both sides, changed on both sides, never one without the other. On screen it reads as one app.

Offline-first. The app keeps its own copy of what the user has already seen. Screens paint from that copy first and correct themselves when the network answers, so opening the app on bad hotel wifi shows the last known state instead of a spinner. Messaging runs over a socket. Everything else is plain HTTP.


Companies get the other half of the same app: post a role, work through the applicants, book the interviews, watch the numbers. One binary, one backend. The account decides which app you land in.


The admin panel is where the client’s own team works: moderation, verification, support, reporting. It calls the same API the apps do, so an action taken there is live in the app, not waiting on a sync.
Nothing reaches the server until the type-check and the production build have both passed in CI. Either one red and the release stops. Mobile waits on store review on top of that. It is the same pipeline every time, which is what makes shipping boring.
Honestly, Yassine's a wizard — he knows his stuff.
The product belongs to the client, so its internals stay with them. Everything above is my half.