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 trick is using if: success() || failure() so the notification fires no matter the outcome but with different messages.
- name: Notify
if: success() || failure()
uses: apialerts/notify-action@v2
with:
api_key: ${{ secrets.API_ALERTS_KEY }}
channel: 'developer'
message: ${{ job.status == 'success' && 'Build deployed' || 'Build failed' }}
tags: 'deploy,staging'
link: ${{ job.status == 'success' && 'https://your-test-link' || 'https://github-run-link' }}
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
- name: Notify
if: success() || failure()
uses: apialerts/notify-action@v2
with:
api_key: ${{ secrets.API_ALERTS_KEY }}
channel: 'developer'
message: ${{ job.status == 'success' && '🚀 Android staging deployed' || '❌ Android staging failed' }}
tags: 'deploy,staging,android'
link: ${{ job.status == 'success' && 'https://appdistribution.firebase.google.com/testerapps/YOUR_APP_ID' || 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
- name: Notify
if: success() || failure()
uses: apialerts/notify-action@v2
with:
api_key: ${{ secrets.API_ALERTS_KEY }}
channel: 'developer'
message: ${{ job.status == 'success' && '🚀 iOS staging deployed' || '❌ iOS staging failed' }}
tags: 'deploy,staging,ios'
link: ${{ job.status == 'success' && 'https://appdistribution.firebase.google.com/testerapps/YOUR_APP_ID' || 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
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
if: success() || failure()
uses: apialerts/notify-action@v2
with:
api_key: ${{ secrets.API_ALERTS_KEY }}
channel: 'developer'
message: ${{ job.status == 'success' && '🚀 Android staging deployed' || '❌ Android staging failed' }}
tags: 'deploy,staging,android'
link: ${{ job.status == 'success' && 'https://appdistribution.firebase.google.com/testerapps/YOUR_APP_ID' || 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