docs
/
Appmint Mobile

Release

Versions, identifiers, permissions and the exact App Store and Play upload flow that has worked.

Identifiers

Value
Packageappmint_mobile, description "Appmint Mobile Management App"
Version1.0.2+9 in pubspec.yaml (marketing version 1.0.2, build 9)
Android application idio.appmint.appmint_mobile
iOS bundle idio.appmint.appmintMobile
Apple teamC66W933VKV
App Store Connect app id6769989899
Launcher iconassets/images/appmint_icon.png via flutter_launcher_icons (adaptive background #6D28D9, alpha removed on iOS, web icons generated)

Environment is chosen at build time: release builds use Environment.production (https://appengine.appmint.io), everything else uses development. See Environment.

Permissions

Android (AndroidManifest.xml): INTERNET, CAMERA, NFC, RECORD_AUDIO, CALL_PHONE, MANAGE_OWN_CALLS, READ_PHONE_STATE, READ_PHONE_NUMBERS, POST_NOTIFICATIONS, FOREGROUND_SERVICE and FOREGROUND_SERVICE_MICROPHONE, BLUETOOTH, BLUETOOTH_ADMIN, BLUETOOTH_SCAN, BLUETOOTH_CONNECT, ACCESS_FINE_LOCATION.

iOS (Info.plist): camera, microphone, Bluetooth always and peripheral, location when in use, photo library and photo library add usage strings, plus UIBackgroundModes for VoIP. Stripe Terminal aborts natively at initTerminal if the Bluetooth usage keys are missing, so keep them.

iOS: App Store

1. Bump the version. A build number alone is not enough once a marketing version has been approved: altool answers 90186 train X closed for new build submissions. Bump CFBundleShortVersionString (the part before + in pubspec.yaml) and the build number.

2. Build the archive.

flutter clean && flutter pub get
flutter build ipa --release --export-options-plist=ios/ExportOptions.plist

ios/ExportOptions.plist uses method: app-store and teamID: C66W933VKV with automatic signing. If the archive sits at "Running Xcode build" with zero CPU, check that an App Store distribution provisioning profile exists for the bundle id and that no Xcode GUI is holding DerivedData; monitor progress by file count under ~/Library/Developer/Xcode/DerivedData/Runner-*, not by CPU.

3. Upload.

xcrun altool --upload-app --type ios -f build/ios/ipa/*.ipa \
  --apiKey <KEY_ID> --apiIssuer <ISSUER_ID>

The App Store Connect API key (.p8) lives under ~/.appstoreconnect/private_keys/ and is discovered automatically. A successful upload prints a Delivery UUID; processing takes 10–40 minutes before the build is VALID.

4. Set export compliance. A new build carries usesNonExemptEncryption: null and cannot be added to a submission until it is set (PATCH /v1/builds/:id with false for standard HTTPS-only apps). The error otherwise is a 409 "not in valid state" whose associatedErrors point at the build.

5. Attach and submit. Through the ASC API: PATCH /v1/appStoreVersions/:id/relationships/build, then POST /v1/reviewSubmissions, POST /v1/reviewSubmissionItems, PATCH reviewSubmissions {submitted: true}. Each version needs its own appStoreReviewDetail with contact name, email, phone in +country format and any demo account; the API has no app-level fallback.

After a rejection, cancel and create a fresh submission

Re-submitting the rejected submission in place returns a persistent STATE_ERROR that never clears. PATCH {canceled: true} on it first, then create a new review submission and add the version.

Also worth knowing: MinimumOSVersion 14.0 must be raised to 15.0 before Spring 2027 (altool warns with 90068).

Android: Google Play

Release builds are signed with the shared central keystore under ~/.android-keystores/ (referenced from android/key.properties). Build an app bundle and upload it to the internal track:

flutter build appbundle --release

Upload is automated with a Play service account under ~/.gplay/; the store listing text lives in STORE_LISTING.md, which still lists version 1.0.0 and needs updating with each release.

Android: direct APK while Play is in review

Play review is not a gate on getting builds to people. tools/release-android.mjs in appmint_go publishes the release APK of every Flutter app in the repo to the appmint-public Space, with a download page:

cd appmint_go
flutter build apk --release            # per app, or all of them
node tools/release-android.mjs         # APPS=appmint_mobile,stowbo for a subset
node tools/release-android.mjs --check # just confirm the credentials resolve

Credentials are S3_KEY / S3_SECRET, read from the environment or from the .env.deploy the hub agent already uses — there is no copy of the secret in appmint_go.

What lands, per app:

PathPurpose
apps/<app>/android/latest/<app>.apkThe link to share. No version in the name, never cached hard
apps/<app>/android/<version>/<app>-<version>.apkThe pinned copy of that build, immutable
apps/<app>/android/…/release.jsonversion, size, sha256, build and publish times
apps/index.htmlThe download page listing every app

The current links:

Page: https://web.appmint.space/apps/index.html

A direct-install APK cannot be updated by Play

The Space copy is signed with the shared release keystore, the Play copy with Play's app-signing key. Android refuses to update one with the other. Anyone who direct-installed has to uninstall before installing from Play — say so when you send the link.

What is in the repository that should not be

certs/ contains appmint-voip-key.pem, the CSR, a .p12, a .pem and a Firebase admin SDK JSON. These are live credentials committed in-tree. Anyone cloning the repository has them; rotate them if the repository has been shared and move them out before any public mirror.

Release checklist

CheckWhy
flutter analyze lib clean of errors and warningsInfos are pre-existing; new errors are not
Version bumped in pubspec.yamlBoth parts for iOS after an approved train
STORE_LISTING.md and CHANGELOG.md updatedListing copy is pasted from here
Dev endpoint not baked inRelease selects production automatically; confirm no override was added
Fast Login still kDebugMode-gatedIt carries real credentials