Skip to content
Back to blog
Case study Marketplace Dhaka

Building YoPekka: how a Bangladesh marketplace handles multi-vendor logistics

YoPekka is a hybrid marketplace + storefront platform serving Bangladesh. The architecture choices we made — single Laravel monolith, queue workers, per-vendor subdomains — and what we'd change.

28 April 2026 · 9 min read · by Virtual Online team
Building YoPekka: how a Bangladesh marketplace handles multi-vendor logistics
YoPekka

YoPekka started with a simple question: Can a marketplace serve Bangladesh-specific commerce — local sellers, bKash/Nagad payments, Pathao/Steadfast couriers, Bengali-first UI — without being a clone of Daraz?

The product is a hybrid: a marketplace surface where buyers shop across vendors, plus per-vendor storefronts on subdomains for sellers who want to build their own brand. Two interfaces, one underlying platform.

Here’s the architecture, the trade-offs, and what we learned shipping it.

Stack at a glance

  • Backend: Laravel 12, PHP 8.4, MySQL 8.4, Redis 7
  • Frontend: Livewire 4 + Alpine.js + Tailwind CSS 4
  • Queue: Redis-backed Laravel Horizon
  • Storage: VPS-local for now, S3-compatible (BunnyCDN) on the roadmap
  • Search: Meilisearch (ditched Elasticsearch — too heavy for the data volumes)
  • Auth: Laravel Sanctum + per-role middleware

Why monolith, not microservices

The temptation in 2026 is to split everything into services from day one. We resisted. A single Laravel codebase with clear module boundaries (cart, catalog, payments, shipping, identity) lets two developers ship features in a week instead of three. We can always split later when one module’s load profile diverges enough to warrant it.

The rule we use: split a service out only when it has different scaling needs, different deploy cadence, or a different team owning it. None of those are true yet for YoPekka. So everything stays in one repo, one deploy artifact, one database.

Per-vendor subdomains

Sellers can opt into a custom storefront at {seller-slug}.yopekka.com. The same Laravel app handles both the marketplace surface and the storefronts — the request middleware detects the subdomain, scopes queries to that vendor, and switches the theme + branding.

The hard part isn’t the routing. The hard part is cache key collisions. A featured-product cache on the marketplace cannot share a key with a featured-product cache on a vendor storefront. We namespace every cache key by request context: cache:marketplace:featured vs. cache:vendor:{id}:featured.

Queue workers for the slow stuff

The pattern we’ve settled into: anything that touches an external API goes through a queue.

  • Order placement: synchronous (cart -> charge -> create order)
  • Inventory decrement: synchronous (must succeed before order confirms)
  • Email confirmations: queued
  • bKash callback verification: queued (occasionally bKash takes 4–8 seconds to confirm)
  • Pathao courier label generation: queued
  • Vendor payout calculations: queued, batched nightly

This split means the checkout page itself responds in 200–400 ms. The slow stuff happens in the background and the user sees their order confirmed instantly.

bKash vs. Nagad behavior

A real-world note for anyone integrating BD payment gateways:

  • bKash charges a token-refresh handshake every 50 minutes. Naive implementations expire mid-checkout for one in 100 buyers. We refresh proactively at the 40-minute mark.
  • Nagad uses a different signing flow per merchant account and is less tolerant of clock skew between your server and theirs. We sync against ntp.ubuntu.com every hour.
  • Both APIs are stable enough for production, but their sandbox environments are not reliable for end-to-end testing. We test final integration on a tiny live amount (৳1) before the official launch.

What we’d change

Two things, looking back at the first 8 months in production:

  1. Schema-level multi-tenancy from day 1. We added vendor_id columns to a dozen tables after launch. Painful but doable. If we were starting over, every domain table would have a tenant scope from the first migration.

  2. Snapshot pricing on orders, not foreign key. When a vendor changes a product’s price, old orders should reflect the price at time of purchase. We initially did order_items.product_id -> products.price (a foreign key lookup at read time). When vendors started price-testing, the historical revenue reports became unreliable. We migrated to snapshot pricing on the order_items row directly.

The Bangladesh-specific UI bets

Three calls we’d make again:

  • Bengali as a first-class UI option, not a translation toggle. Bengali text wraps differently, breaks lines differently, has different optical sizing requirements than English. Our Bengali stylesheet has its own font (Noto Sans Bengali Variable) and its own line-height ratios. The difference is invisible to readers but huge to readability.
  • WhatsApp routing for support, not a chat widget. Bangladesh buyers will message a WhatsApp number with a screenshot 30x more often than they’ll type into a chat widget. We route to a real number with auto-routing to the right seller.
  • COD as a peer of bKash, not an afterthought. 28% of YoPekka orders are COD. We give it equal real-estate at checkout, equal trust signals, equal confirmation flow.

The marketplace is profitable. Vendor signups grow 12-15% month over month. Most importantly, the codebase is still readable — one developer can hold the whole architecture in their head, which is the only stack choice that actually matters at this size.

Tags
#yopekka#laravel#marketplace#architecture#queues

Got a similar project in mind?

We're delivering 30-40 websites a month across Bangladesh. The story above is one of them — yours could be next month's post. Let's talk.

Available for new projects

Ready to build something great?

Tell us about your project. We reply within a few hours, and you'll get a quote within the day — no long discovery forms, no agency-speak.