Running a release train for two app stores

Shipping a mobile app weekly to two stores is a scheduling problem before it is an engineering one. Two review processes with different temperaments, a binary you cannot hotfix, and users who upgrade whenever they feel like it. The teams that manage it are not faster. They are more boring.
Web deployment habits do not transfer. On the web, a bad release is reversible in minutes and the fix reaches everyone at once. On mobile, the previous version is still installed on most phones, the new one sits behind a review queue you do not control, and a meaningful share of users will not update for weeks. Every part of the process has to be designed around those three facts.
The train leaves on a schedule, not on readiness
The single change that fixes most mobile release chaos is deciding that the release goes out on a fixed day whether or not a particular feature is finished. If it is not merged and green by the cut, it catches the next one.
Releases that wait for a feature accumulate more features while they wait, and a release with eleven changes in it is far harder to diagnose than two releases with five and six. The train also removes the negotiation. Nobody has to argue for a slip, because there is another train in seven days.
The objection is always that a fixed cadence ships less. In practice it ships more, because the cost of missing a train is one week rather than an indeterminate wait, so nobody rushes an unfinished feature in to avoid being stranded. Rushed features are the main source of the releases that then have to be pulled.
Wednesday is not arbitrary. Review times vary, but it is the tail that hurts, and a Wednesday cut means even a slow review lands inside the working week. Friday submissions are the most common self-inflicted wound in mobile delivery: the rejection arrives Saturday, nobody sees it until Monday, and a two-hour fix has cost three days.
Separate shipping the code from launching the feature
Every incomplete feature ships dark behind a remote flag. The binary contains it, the store has reviewed it, and nobody sees it until the flag opens. This decouples the two things that would otherwise be tangled: a store review cycle you do not control, and a launch date marketing has already committed to.
It changes what QA tests, too. Instead of testing a release, we test each flag state independently, which means the combination that reaches users is one we have already seen. It also means an unfinished feature can sit in the binary for three weeks without blocking anything, which removes the pressure to merge something before it is ready.
It also gives you the only meaningful undo mobile offers. Rolling back a bad release means waiting for another review. Closing a flag takes seconds and reaches users who have already installed the build.
One discipline goes with it: flags need an expiry. A codebase with forty stale flags is harder to reason about than one with none, and every flag is a branch in behaviour someone has to hold in their head. We remove a flag in the sprint after its feature reaches full rollout, and that removal is part of the definition of done rather than a cleanup task nobody schedules.
Rejections are scheduling events
A rejection is not an incident. It is a normal outcome with a known distribution, and the correct response is a prepared one rather than an improvised one.
Tracking rejection reasons over time is worth the small effort. Most teams find two or three recurring causes, usually around account deletion, subscription disclosure, or permission justification strings. Once they are known they become part of the pre-submission checklist and stop happening, which is a better outcome than getting faster at appealing them.
The playbook exists mainly so the decision is not made by whoever happens to be online. A rejection at four on a Thursday afternoon is a situation where a tired person reaches for the most cautious option, which is usually to hold everything. Written down in advance, the same situation takes a minute and the train leaves.
Roll out staged, with the halt criteria written first
Both stores support phased rollout and it should always be on. Ours runs at roughly one percent, then ten, then fifty, then everyone, with at least a few hours between each step and a full day before the last one.
Android makes this straightforward and iOS phased release is coarser, so the two stores will not move in lockstep. That is acceptable. What matters is that neither reaches everyone before you have seen a full day of real usage on a meaningful sample, because the failures that matter are rarely the ones that appear in the first hour.
The important part is agreeing the halt condition before the release, in a number. Crash-free sessions below 99.5 percent halts the rollout. Deciding that during a rollout, while looking at a dashboard that is already moving, produces a worse answer every time.
Write the halt criteria as numbers with an owner, and make halting cheap and unremarkable. If stopping a rollout requires a discussion, people find reasons not to stop. If it is one person, one threshold and one button, it happens early, when the affected population is one percent rather than everyone.
A mobile release is the one deploy you cannot take back. Every part of the process should assume the last build is the one users are stuck with.
What we watch
Crash-free sessions per version, not in aggregate, because an aggregate hides a bad release behind a good installed base. Adoption curve, which tells you when the old version stops mattering and an old API can finally be retired. Review turnaround per store, kept as a running average so the buffer is set from data. And time from merge to available, which is the number that actually describes how fast the team ships.
On the SeaRebbel app that last figure sits at nine days, most of which is the train waiting for its scheduled slot. That is deliberate. Predictability is worth more than the four days we could save by cutting whenever something is ready.
The number worth improving is rarely the calendar. It is the share of releases that reach full rollout without a halt, which on a healthy train sits above ninety percent. When that figure drops, the problem is upstream in test coverage or scope, and speeding up the train would only deliver the same defects sooner.



