I Keep Forgetting About My Long-Running Mobile App Builds
January 15, 2026
If you’ve ever built mobile apps with CI, you know the pain. You push a PR, the build kicks off, you context switch, and then forget about it. An hour later you check the Actions tab and discover it failed 45 minutes ago. Or it succeeded and QA has been waiting on it the whole time.
This has been happening too many times for me to count, so I built something to solve it.
The problem
There’s no way to automatically know when your build is done. You just need to keep an eye on the Actions tab or wait for your PR to become mergeable.
What I actually wanted:
- A push notification on my phone the moment the build finishes
- A direct link. Tap the notification, land on Firebase App Distribution or TestFlight, install immediately
- Different messages for success and failure
- A link to the GitHub Actions run for failed builds so I can see what went wrong
- No waiting around, no checking every 10 minutes, no post-it notes under the monitor
How it works
I’m using the API Alerts GitHub Action. It runs as a step at the end of your workflow and sends a push notification to your phone.
The key feature for CI/CD is that you can attach a link to the notification. Tap the notification and it takes you exactly where you need to go.
The pattern I use is two separate notify steps with if: success() and if: failure() so each outcome has its own clean configuration. GitHub Actions skips steps after a failure by default, so the if: guards are what make each branch fire.
- if: success()
uses: apialerts/notify-action@v2
with:
event: ci.build.success
channel: developer
message: 'Build deployed'
tags: deploy,staging
link: 'https://your-test-link'
- if: failure()
uses: apialerts/notify-action@v2
with:
event: ci.build.failed
channel: developer
message: 'Build failed'
tags: deploy,staging
link: ${{ format('{0}/{1}/actions/runs/{2}', github.server_url, github.repository, github.run_id) }}
I set APIALERTS_API_KEY once at the job level so I don’t have to repeat the credential on every notify step (more on that in the full workflow below).
Note the event: field. It’s optional but recommended, and it’s what lets you route different build outcomes to different destinations in 2.0 (a Slack channel for failures, a celebratory webhook for successes, etc.) without changing any of this workflow code. Routers match it with Unix glob (fnmatch), so a pattern like ci.* catches every CI event. Dotted keys like ci.build.failed are a clean convention, but free-form keys with spaces (User Signup matched by User *) work too.
My Android setup
I upload to Firebase App Distribution, then notify. If the build worked, the link takes me straight to App Distribution to install. If it failed, the link goes to the Actions run.
- name: Firebase App Distribution
uses: wzieba/Firebase-Distribution-Github-Action@v1
with:
appId: ${{ secrets.ANDROID_FIREBASE_APP_ID }}
serviceCredentialsFileContent: ${{ secrets.GCP_CREDENTIALS }}
groups: staging
file: app/build/outputs/apk/release/app-release.apk
- if: success()
uses: apialerts/notify-action@v2
with:
event: ci.android.staging.success
channel: developer
message: '🚀 Android staging deployed'
tags: deploy,staging,android
link: 'https://appdistribution.firebase.google.com/testerapps/YOUR_APP_ID'
- if: failure()
uses: apialerts/notify-action@v2
with:
event: ci.android.staging.failed
channel: developer
message: '❌ Android staging failed'
tags: deploy,staging,android
link: ${{ format('{0}/{1}/actions/runs/{2}', github.server_url, github.repository, github.run_id) }}
iOS with Fastlane
I use the same approach for the iOS app. My Fastlane workflow tests, builds, deploys to Firebase App Distribution (or TestFlight), then sends the notification:
- name: Test App
run: fastlane ios tests
- name: Build
run: fastlane ios build_beta
- name: Deploy
run: fastlane ios deploy_beta group:staging
- if: success()
uses: apialerts/notify-action@v2
with:
event: ci.ios.staging.success
channel: developer
message: '🚀 iOS staging deployed'
tags: deploy,staging,ios
link: 'https://appdistribution.firebase.google.com/testerapps/YOUR_APP_ID'
- if: failure()
uses: apialerts/notify-action@v2
with:
event: ci.ios.staging.failed
channel: developer
message: '❌ iOS staging failed'
tags: deploy,staging,ios
link: ${{ format('{0}/{1}/actions/runs/{2}', github.server_url, github.repository, github.run_id) }}
If you’re using TestFlight, swap the success link for your App Store Connect URL or the itms-beta:// deep link so testers can install directly.
Keeping staging and production separate
I use separate channels to keep things organised:
developerfor staging and feature branch builds. Just me.releasesfor production builds. The messages are different too:
# Staging
channel: 'developer'
message: ${{ job.status == 'success' && '🚀 Android staging deployed' || '❌ Android staging failed' }}
# Production
channel: 'releases'
message: ${{ job.status == 'success' && '🚢 Android production ready to submit' || '❌ Android production failed' }}
For production, I point the success link to the Play Store listing or App Store Connect instead of Firebase.
Full workflow
This is the actual workflow I use to build the API Alerts Android app. It’s a Compose Multiplatform project, but the pattern works for any Android build.
name: Android - Staging
on:
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
env:
APIALERTS_API_KEY: ${{ secrets.API_ALERTS_KEY }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'adopt'
cache: gradle
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Create Keystore
uses: timheuer/base64-to-file@v1
with:
fileDir: 'app'
fileName: 'upload.jks'
encodedString: ${{ secrets.ANDROID_UPLOAD_KEYSTORE }}
- name: Create key.properties
run: 'echo "$KEY" > key.properties'
shell: bash
env:
KEY: ${{ secrets.ANDROID_KEY_PROPERTIES }}
- name: Create google-services.json
run: 'echo "$KEY" > app/google-services.json'
shell: bash
env:
KEY: ${{ secrets.ANDROID_GOOGLE_SERVICES }}
- name: Test
run: ./gradlew test
- name: Build
run: ./gradlew assembleRelease
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: Android Staging
path: app/build/outputs/apk/release/app-release.apk
retention-days: 7
- name: Firebase App Distribution
uses: wzieba/Firebase-Distribution-Github-Action@v1
with:
appId: ${{ secrets.ANDROID_FIREBASE_APP_ID }}
serviceCredentialsFileContent: ${{ secrets.GCP_CREDENTIALS }}
groups: staging
file: app/build/outputs/apk/release/app-release.apk
- name: Notify success
if: success()
uses: apialerts/notify-action@v2
with:
event: ci.android.staging.success
channel: developer
message: '🚀 Android staging deployed'
tags: deploy,staging,android
link: 'https://appdistribution.firebase.google.com/testerapps/YOUR_APP_ID'
- name: Notify failure
if: failure()
uses: apialerts/notify-action@v2
with:
event: ci.android.staging.failed
channel: developer
message: '❌ Android staging failed'
tags: deploy,staging,android
link: ${{ format('{0}/{1}/actions/runs/{2}', github.server_url, github.repository, github.run_id) }}
The workflow now
- Push code to a PR
- Context switch. Make a coffee, quick power nap, play with the cat, review another PR, whatever
- Phone buzzes: ”🚀 Android staging deployed”
- Tap → Firebase App Distribution opens → install → test
Or:
- Phone buzzes: ”❌ Android staging failed”
- Tap → The GitHub Actions run opens → see exactly what broke
The entire process from “push code” to “testing the build on my phone” now requires zero attention. No more forgetting about builds, no more refreshing the Actions tab, and no more QA wondering why the build they asked for at standup still isn’t dev tested.
And yes, I did turn off the Firebase App Distribution emails. All my dev notifications are in one place now, and if I ever want to go back and look at build history, every event is logged in the mobile app and the web dashboard.
It’s not just mobile
I’ve taken this approach and applied it to my backend deployments, web builds, and even SDK releases. Any build or deployment that takes more than a minute and may fail now sends a notification.
Links
- GitHub Actions integration docs for the full input reference
- Channels docs for separating staging from production
- CLI docs if you want to send alerts from local scripts and cron jobs too