---
title: Inside SWSH's automated mobile release pipeline with Expo
authors: Dan Kelly
published: August 26, 2026
categories: Users, React Native
tags: CI/CD, App Clips, React Native, OTA Updates
---

> _"Expo's ecosystem helped us take human error and effort out of the release process, from a local build to a validated release."_
>
> — Nathan Ahn, CTO, SWSH

## One codebase, every platform

[SWSH is a social photo-sharing app](https://apps.apple.com/us/app/swsh-shared-photo-albums/id6476418833) for shared albums and events. You might expect a team shipping to iOS, an iOS App Clip, Android, and the web to be juggling four sets of build tooling and a release checklist to match. A single React Native codebase powers all of it, and a release rarely needs anyone to push it along.

![SWSH on the app store](https://cdn.sanity.io/images/9r24npb8/production/8add8758eeddf22a599e233d8ae8558d389c25b5-2400x1140.jpg)

This case study looks at how SWSH got there: 300 builds a month on its own hardware, signing credentials the team never touches, App Store Connect driven through an API instead of a browser, and a home cooked end-to-end suite that validates every release before it goes out.

The web deploys on its own track, so the rest of this story is about the three native targets. A change starts as a local build, runs through an end-to-end test suite on iOS simulators and Android emulators, and lands in both app stores with minimal human involvement. Native releases go out about once a week, and the pipeline is what gets them there safely.

## Making a release a non-event

If you've shipped a mobile app, you know the chain: cut the builds, handle the signing credentials, submit to App Store Connect and Google Play, then confirm nothing regressed on the way out. Done by hand, every one of those steps is a place a release can quietly break, and the more often you ship, the more those mistakes cost you.

So SWSH went after the whole chain at once: **automate the entire release pipeline so human error has nowhere to enter.** Expo's build and submit tooling gave them that automation out of the box, plus a foundation solid enough to build the rest on top.

## Why Expo is the best infrastructure for running an autonomous release pipeline

Expo gave SWSH a single automated path from source to store: builds, credentials, submission, and over-the-air updates.

[Continuous Native Generation](https://expo.dev/blog/what-is-continuous-native-generation) (`expo prebuild`) lets the team regenerate native projects on demand instead of hand-maintaining Xcode and Gradle projects, and they can still [drop into native code](https://expo.dev/blog/expo-ui-stable-sdk-56) when a feature calls for it. And because EAS runs the _same_ build tooling locally or in the cloud, SWSH could pick up EAS's orchestration, credential management, and submission without giving up control over where builds actually run.

## Inside SWSH's release pipeline

### Local builds at scale: EAS Build

For nearly every mobile team in the world [Expo's cloud builds](https://docs.expo.dev/build/introduction) are the prudent choice. SWSH is the exception, and the reason is volume: full iOS and Android builds on every pull request that touches native code, plus per-environment release builds, adding up to **300 a month**. At that scale the math changes, so the team moved compilation onto its own self-hosted macOS and Android runners using [**local EAS builds**](https://docs.expo.dev/build-reference/local-builds/).

This is the part that's easy to miss about EAS. Because `eas build` behaves the same locally as it does in the cloud, SWSH kept the orchestration, the credential handling, and the submission, and moved only the heavy compilation step onto its own hardware.

### Credentials the team never manages: EAS

Signing credentials for both App Store Connect and Google Play live in EAS. The only secret SWSH's CI holds is a single Expo access token. That's it. Code signing is the part of a mobile release you least want floating around in a CI config or a repo, and for SWSH it simply isn't there.

### Submitting to both stores: EAS Submit and a custom App Store Connect pipeline

![Agentic Release pipeline](https://cdn.sanity.io/images/9r24npb8/production/59133097daa5993a0aa1df7e89a65cfb9d5f6f14-2400x1296.jpg)

EAS Submit handles submission for both stores, pushing each build straight to Google Play and App Store Connect with no custom glue. On Google Play that's the whole job. iOS asks for more around the binary: a fresh App Store version, metadata and release notes, a configured App Clip experience, and a submission for review.

So SWSH pairs EAS Submit with an App Store Connect pipeline of its own. Once the build is uploaded, that pipeline drives the App Store Connect API directly. It polls for the processed build, creates the App Store version, and sets the metadata and "What's New" text. Then it configures the App Clip's default experience, down to the card subtitle and card image, and submits the version for review.

If you've ever set up an App Clip experience by hand, you know how many windows that is. SWSH turned the whole thing into one more automated step in the release.

> SWSH manages its App Store metadata through a custom pipeline rather than [EAS Metadata](https://docs.expo.dev/eas/metadata/), Expo's declarative metadata solution. Most teams won't need to build one and explore EAS Metadata instead.

### Validating every release: Meridian

Before anything ships, SWSH runs **Meridian**, its in-house end-to-end testing framework built on Appium and WebDriverIO and driven by Vitest. Meridian exercises the flows that actually matter, login, sign-up, upload, and deep linking, across **iOS, the App Clip, and Android** on simulators and emulators in CI. So when SWSH calls a release validated, it means the app was driven through those flows, not just compiled successfully.

![SWSH validation gate and E2E Testing](https://cdn.sanity.io/images/9r24npb8/production/9a41d9b7181e4ab516eb65ebbafb24a9ec95f84a-2400x1398.jpg)

### Shipping JS fixes: EAS Update

For changes that don't touch native code, [EAS Update](https://docs.expo.dev/eas-update/introduction) lets SWSH push JavaScript fixes immediately.

Meridian validates those too, and this part is worth copying. An EAS Update delivers new JavaScript onto a native build that's already in users' hands, so SWSH runs the suite against the updated bundle to confirm the new code works with the build it's landing on and doesn't introduce regressions.

### Catching regressions in production: EAS Observe

Shipping isn't the end of the release. SWSH uses [EAS Observe to watch app performance in production](https://expo.dev/solutions/expo-observe), tracking runtime metrics like first render and time-to-interactive. When a release introduces a startup or rendering regression, it shows up there, and the team can push a fix over the air.

## The impact of an automated release pipeline

Engineers push their changes and move on. The pipeline builds, validates, submits, and monitors each release without them, and it does that across roughly 300 builds a month, a native release about once a week, and JavaScript fixes going out over the air the same day.

SWSH is proof that shipping often and shipping safely aren't a trade-off. Get the pipeline right once, and the attention that used to go into release logistics goes back into the app.