case 03 / 05in build
Sultana Express
A B2B sourcing marketplace for Turkish manufacturing, rebuilt as a platform.
- role
- Co-founder and CTO, leading the rebuild
- period
- beta November 2026
- platforms
- web consoles · iOS
- public
- sultana.express
at a glance
- A B2B marketplace that connects buyers with Turkish mills, factories and cooperatives.
- Rebuilt from a no-code prototype into a Go monolith, three Next.js consoles and native iOS apps.
- Money moves through a double-entry ledger, and escrow releases on the buyer’s quality acceptance.
- Consoles in English, Turkish and Arabic, right-to-left by construction.
- A 145-phase plan, built by AI agents under deterministic CI gates.
- phases in the plan, 651 sub-phases
- 145
- OpenAPI operations over 467 paths
- 556
- architecture decision records
- 45
- visual-regression baselines
- 4,430
Context
Sultana Express connects buyers with Turkish manufacturers. Wholesalers, brands and hospitality groups find verified mills, factories and cooperatives, send requests for quotes, compare and negotiate, pay into escrow, and follow samples, production, inspection, shipping and customs.
Suppliers handle verification, catalogs with volume pricing, capacity, sampling, production and payouts; operators run verification reviews, disputes, queues and analytics. It starts with textiles, and a new sector arrives as data.
As co-founder and CTO, I’m rebuilding it from a no-code prototype into a Go, Next.js and SwiftUI platform. The marketing site sultana.express is live, and the beta is announced for November 2026.
Constraints
- Money and personal data: every money mutation needs an idempotency key, and personal data must be erasable while legal records stay.
- Three languages in the consoles, English, Turkish and Arabic, with right-to-left decided on the server.
- A new sector must need no change to the engine or the schema.
- The apps in use keep running on the old backend while the rebuild happens.
- Agents write the code; deterministic gates, not models, decide what merges.
Architecture
One Go codebase builds three binaries, the API, the worker and the realtime gateway, each deployed and scaled on its own. It holds 38 modules in 7 clusters, each a four-layer slice behind interfaces, and lint rules keep the boundaries.
The rebuild inverts the old stack: the Go service is the only trusted decider, and the database stores rows without deciding who may read them.
The API is contract-first: OpenAPI 3.1 generates the server and the web client, and CI fails on drift or a breaking change. Events leave only through an outbox row written in the same transaction as the change.
- API serviceGo, cmd/api
- to Postgres: one tx
- Postgres storestate + outbox
- to Outbox relay: notify
- Outbox relay processsingle, locked
- to JetStream: publish
- JetStream queueper-cluster
- to Consumers: durable
- Dead letters queueper consumer
- Consumers servicededupe by id
- to Dead letters: failed
- to Search: project
- Search storeprojection only
- A state change and its outbox row commit in one transaction.
- One relay holds the lock and publishes.
- JetStream drops duplicates by message id.
- Consumers dedupe on the event id; each has its own dead-letter queue.
- Search is a projection, never the source of truth.
Decision log
Where the rules live
- options
- rules in database policies and functions
- the Go service decides; the database stores
- chose
- the Go service decides; the database stores
Escrow release
- options
- on delivery
- on the buyer’s quality acceptance
- chose
- on the buyer’s quality acceptance
Search
- options
- query the database
- a projection fed by the outbox
- chose
- a projection fed by the outbox
- why
- Only the worker holds a write credential, and search is never the source of truth.
Sessions in the browser
- options
- tokens in browser storage
- httpOnly cookies behind a BFF
- chose
- httpOnly cookies behind a BFF
- why
- Tokens never reach page JavaScript, and a test in a real browser proves it.
Money moves when goods do
deep diveAn append-only, double-entry ledger is the only authority on balances. Every movement balances to the kuruş at commit, no role can edit or delete a written entry, balances are computed from history, and money is whole minor units, never floating point.
The payment provider sits behind a port, and its webhooks are the only proof that money moved, so a duplicate notification cannot post twice. Escrow releases on the buyer’s quality acceptance, and payouts wait for the supplier’s verification. A merge-blocking wall proves these invariants, down to exactly one reversing entry under a genuine concurrent race.
∑ = 0T+1 releasepayout after KYB
The request gauntlet
deep diveEvery tenant row carries its organization, and the active organization comes from the session on the server. One permission check per request runs in a chain that denies by default, and a refusal never says whether a resource exists.
- Browser clientno token in JS
- to Cookie BFF: cookie
- Cookie BFF servicecookie → Bearer
- to Session check: Bearer
- Session check processverified token
- to Active org: claims
- Active org processfrom session
- to Permission: org_id
- Postgres storeorg_id per row
- Handler serviceorg-scoped
- to Postgres: org rows
- Permission processone authority
- to Handler: allowed
- to Refusal: denied
- Refusal processreveals nothing
- The session lives in an httpOnly cookie; page JavaScript never holds a token.
- The BFF swaps the cookie for a Bearer token on the server.
- The API verifies the session.
- The active organization comes from the session, never from the request.
- One permission check decides; anything not allowed is denied.
- Only then does the handler run, scoped to the organization.
- A refusal never says whether the thing exists.
Gates that prove their own teeth
deep diveAgents write the code, so the gates carry the weight. Each phase gate plants deliberate defects on every run, and each must be rejected by name; a wall is also tested with one of its arms secretly switched off.
A language model’s review is never the only gate for payments or personal data.
- every editsecrets, card numbers, linters, float money
- reviewer agentssecurity, correctness, contract, privacy, UX
- pre-commit
- the CI wallabout 45 merge-blocking checks
- scheduled jobs
Adding an industry is a data entry
deep diveEach industry is a data pack: product types, attributes, units, and its manufacturing, inspection and certificate rules. Food and agriculture was the maximum-difference test: 14 product families, origin, grade and harvest-year attributes, selling by the kilogram and the tonne, and harvest lots with aflatoxin quarantine. Home textiles was the maximum-reuse test. Neither needed an engine or schema change, and a CI gate registers a throwaway fifth sector end to end on every run.
Stack
frontend
- Next.js 16 Multi-Zones
- the buyer, supplier and operator consoles, on one domain
- React Aria and Tailwind CSS 4
- accessible components on design tokens
- next-intl
- English, Turkish and Arabic, with right-to-left decided on the server
backend
- Go 1.26
- one modular monolith: the API, worker and realtime binaries
- OpenAPI 3.1 and AsyncAPI
- the contracts the server and the client are generated from
data
- PostgreSQL
- the system of record and the ledger
- NATS JetStream
- durable domain events
- Redis
- cache, rate limits and the idempotency fast path
- Typesense
- the search projection
- S3-compatible storage
- private media behind presigned URLs
ai
- Claude Code
- one headless session per sub-phase, with reviewer agents
mobile
- SwiftUI
- buyer and supplier apps on a shared package
infra
- k3s and Argo CD
- the GitOps production target
other
- Playwright and axe
- the visual and accessibility walls
Design
This site’s diagram and motion language comes from this project: a design system with 22 animated diagrams under six motion laws, semantic motion from three primitives, and CSS-only SVG. Its tokens come in three layers and flow into CSS variables, a Tailwind preset and a DTCG export. The consoles’ redesign is under way on design branches.
wc -l
as of Oct 2026- phases in the plan, 651 sub-phases
- 145
- OpenAPI operations over 467 paths
- 556
- architecture decision records
- 45
- visual-regression baselines
- 4,430
- modules in 7 clusters, one Go monolith
- 38
- AsyncAPI event channels
- 167
- database migrations
- 261
- web zones, sharing 7 packages
- 5
- visual-regression wall, sharded from 68m46s
- 16m45s
Timeline
- The clean-rebuild plan
- The backend build starts
- The marketing site goes live
- The supplier console built and gated
- The backend through Stage 5, and the buyer console built and gated
- The operator console built and gated
- Betaannounced
Status and next
In build and pre-launch. What comes next is drawn faint until it ships.
- backendthrough Stage 5
- three consolesbuilt and gated
- design overhaulon design branches
- production and live payments
- betaannounced for November 2026