DEV Community

K M Shahriar Hossain
K M Shahriar Hossain

Posted on Originally published at devshakib.jumyn.com

What It Actually Takes to Run a Cross-Border Marketplace: Six Years of Shpper

Shpper is a cross-border personal-shopping marketplace. A buyer wants something
they cannot get where they live. A traveller is already flying that route with
unused luggage space. The platform introduces them, holds the money until the
item arrives, and turns an empty few kilos in someone's suitcase into a delivery
network.

I am the CTO. I own the platform end to end — the Flutter apps for both sides,
the backend, payments and escrow, identity verification, and the release
pipeline. We are on version 14.2.0, rated 4.64 on the App Store from 72 ratings.

Fourteen major versions is enough distance to say something useful about what
this kind of product actually demands. Almost none of the hard parts were the
ones I expected.

You are not building one app

The first structural fact about a two-sided marketplace is that "the app" is two
products with opposed interests, and you ship both.

The buyer wants their item cheaply, quickly, and with certainty it will arrive.
The traveller wants to be paid well, carry as little risk as possible, and not be
blamed for customs. Those are not complementary desires. Every feature has to be
designed twice, from two points of view, and any change to the shared middle —
the request, the offer, the trip — reaches into both.

This has a consequence people underestimate: your release cadence is bounded by
the slower side.
A change to how offers work is not shipped when the buyer app
ships. It is shipped when both apps are live, on both platforms, and enough of
both populations have updated. Mobile app review is not a build step you can
optimise away; it is a scheduling constraint that shapes how you design changes,
which is why so much of the logic has to be able to change without a release.

The hard problem is trust, and it is not a feature

A marketplace's actual product is trust between strangers. Everything else is
plumbing.

Consider what the platform is asking. A buyer sends money for an item that does
not exist yet, to be bought by someone they have never met, in a country they
are not in, and carried across a border. A traveller agrees to spend their own
money on someone else's goods, in exchange for a promise of repayment, and to
carry those goods through customs under their own name.

Neither side would do that for a stranger. They do it because the platform
stands between them, and everything about the engineering follows from that
position:

Escrow is not a payments feature, it is the entire trust mechanism. Money is
held from the moment a deal is struck until the item is confirmed delivered. That
single design decision is what makes the rest possible — the buyer risks nothing
until they have the item, and the traveller knows the money genuinely exists
before they spend their own.

Identity verification is what makes escrow meaningful. Held funds only
protect you if there is a real, identified person on the other end when
something goes wrong. Verification is not a compliance checkbox bolted on for
regulators; it is the thing that makes the escrow promise enforceable.

The state machine is the product. A request becomes an offer, becomes an
accepted deal, becomes a purchase, becomes a carried item, becomes a delivery,
becomes a release of funds. Every transition can fail, and each failure needs a
defined answer: what happens to the money, what each side sees, who is told what
and when. The unhappy paths outnumber the happy one, and they are where the
product either holds together or falls apart.

Money makes everything irreversible

Ordinary app bugs are annoying. Bugs that touch money are a different category,
because you usually cannot fix them by shipping a patch.

A double-charge is not resolved by correcting the code — the money has already
moved, and now there is a human on the other end who is angry and correct to be.
A payout that fires twice is gone. A state transition that releases escrow early
cannot be un-released by a hotfix.

This changes how you write things. Every money-moving operation has to be
idempotent, because networks retry and users tap twice and both of those are
normal. State transitions have to be atomic, because a partial write in the
middle of a payment is worse than a failure. And ledgers have to be
append-only — the record of what happened is not something you edit, because the
moment it is editable it stops being evidence.

None of this is exotic. All of it is the difference between a bug you fix and a
bug you refund.

Cross-border is a category of its own

Everything above applies to any escrow marketplace. Crossing a border adds
problems that are not software problems at all, and the engineering has to
absorb them anyway.

Prices move while a deal is open, because currencies move. Customs and duty are
real costs that land on somebody, and the product has to be unambiguous about who
before anyone commits, not after. Items that are perfectly ordinary in one
country are restricted in another. Trips get cancelled, delayed, or rerouted, and
a delivery network built from other people's travel plans inherits all the
volatility of those plans.

The engineering lesson is that a design which assumes stability will spend the
rest of its life being patched. Timelines slip, prices change, routes vanish.
Building for that from the start is much cheaper than discovering it across
fourteen versions.

What being CTO of this actually means

The title suggests architecture diagrams. The reality is that owning it end to
end means the boundary between "engineering problem" and "business problem" does
not exist.

When a delivery goes wrong, the question is not only what the code did. It is
what the policy should be, who absorbs the cost, what both users are told, and
what changes so it happens less often. That is one conversation, not four, and
being the person who understands both the state machine and the commercial
consequence is most of the job.

The other half is choosing what not to build. Every marketplace has an infinite
backlog because both sides always want more, and the sides want different things.
Fourteen versions in, the decisions I am most glad about are almost all
subtractions.

The stack, briefly

Flutter for both apps, so one codebase covers iOS and Android on each side —
which matters enormously when "shipping a feature" already means shipping two
products. Firebase for the backend. The choice that has aged best is not a
technology but a principle: keep as much behaviour as possible changeable
without an app release
, because you cannot hotfix your way out of a marketplace
problem when app review sits between you and your users.


The thing I would tell someone starting one of these: you are not building
software that connects buyers and sellers. You are building the reason two
strangers are willing to take a risk on each other, and every technical decision
either strengthens that or quietly erodes it. Escrow, verification, and an
honest state machine are not features on a roadmap. They are the product, and
everything else is how it is delivered.

Shpper is at shpper.com, and there is a longer
case study on this site.


Originally published at devshakib.jumyn.com. I write about Flutter, Dart and the parts of shipping that are genuinely awkward — and publish the packages that came out of them at pub.dev/publishers/jumyn.com.

Top comments (0)