A guide to setting up the distribution of iOS applications to your users using "custom apps"

Here’s a thorough guide on the recommended process to distribute an app to iOS devices.

Introduction

With enterprise apps being discouraged by Apple, private / “custom apps” are becoming the only viable option to distribute iOS apps.

There are two components to distributing your “custom apps”:

  1. Apps must be published on the App Store, with distribution restricted to one or more specific organizations.
  2. Your customer organization* must have an Apple Business account.

Typically you, as a customer of JourneyApps, will be responsible for both (1) and (2). All you need to provide JourneyApps is the provisioning profile and private key (optional) for us to create the builds for you to distribute.

To proceed below you need an Apple developer account. Registering for an Apple developer account is outside the scope of this post.

*Customer Organization means the organization that the end-users belong to. There may be more than one, in which case each needs an Apple Business account.

Publishing “custom apps” to the App Store

The process is 99% the same as publishing an app to the public App Store. The same review process is required. This means there are some caveats to take into account versus enterprise builds:

  1. A full description of your app and screenshots are required.
  2. Apple needs a test account for reviewers to test the app. This needs to be a functional app, and should match the app’s description. If your app only uses SSO, the reviewer will need an account on that SSO system.
  3. All updates need to go through the review process, which at the time of writing takes around 2 days.

Prerequisites:

  • Accept agreements for Free Apps and Paid Apps, even if the app will not be a paid app. Someone with the Legal role will need to do this.

Bundle ID and provisioning profile

What follows are instructions to get the Bundle ID and provisioning profile, so that you can share that with JourneyApps:

  1. Create an Identifier (Bundle ID) on the App Store account.
  • Start here: https://developer.apple.com/account/resources/identifiers/list

  • Type: App IDs → App

  • Description: anything, default to “ AppStore”

  • Bundle ID: You can find out from JourneyApps what the bundle ID is for a specific container. It has to be globally unique - we cannot use the same one for enterprise app and App Store.

  • Capabilities:

    1. Select “Push Notifications” (required, regardless of whether or not it will be used).
    2. [OPTIONAL] Enable “NFC Tag Reading” only if it will be used.
    3. “Game Center” and “In-App Purchase” are always enabled.
  1. Create an “iOS Distribution” certificate.
  • If you have already created a certificate for a separate iOS application, this step does not need to be repeated. Only one distribution certificate is required per developer account (and can be used for multiple apps), but multiple are allowed. This has to be renewed every year.
  • Start here: https://developer.apple.com/account/resources/certificates/list
  • JourneyApps can create and manage a private key, which is used in the JourneyApps Container build process.
  • Alternative: You may also use your own private key. Then JourneyApps can opt to skip signing during the build process so that you can sign containers manually (outside the scope of this post).
  1. Create an “App Store” (provisioning) profile.
  • Start here: https://developer.apple.com/account/resources/profiles/list
  • Type: Distribution → App Store
  • App ID: Select the Identifier from step 1.
  • Certificate: Select the certificate from step 2.
  • Provisioning profile name: Use same as bundle ID description.
  • Download the provisioning profile, and share with JourneyApps.

After you’ve share the provisioning profile and distribution certificate [Optional] with JourneyApps, we are able to create a branded JourneyApps container IPA that we will share with you. You can then submit this container to the App Store for review. More details below.

App Store Submission

Next you need to create an app on the App Store, using the same developer account (this can be done in parallel with the previous steps):

  1. Start on App Store Connect → My Apps: https://appstoreconnect.apple.com/WebObjects/iTunesConnect.woa/ra/ng/app and create new app.
  • Name: Must be globally unique on the App Store, e.g. “JourneyApps Snaglist” instead of “Snaglist”. Does not have to match the app / home screen name.
  • Bundle ID: Pick previously created one.
  • SKU: Anything, but use the Bundle ID as a convention.
  1. User Access: Default to Full Access. Affects developer users that can access the app, not end-users.

  2. Complete all the required App Info:

  • Privacy Policy: Provide your own privacy policy.
  • Category: Default to Productivity
  • Age rating: Complete the survey, and the result should end up as “Ages 4+”, but not “designed for kids”.
  1. Pricing and availability:
  • Price Schedule: Free
  • Availability: All Countries
  • Distribution for Business and Education: Choose the last option - “Available for private distribution to specific organizations on Apple Business Manager or Apple School Manager”
  • Organization ID and name: Available on the Apple Business Portal (see section below).
  1. Add an app version.
  • Description: Make it clear that this is a private app intended for the specific customer only.

  • Build: Add the IPA we shared with you.

  • App Review Information: Sign-in required; add some test credentials and notes for the reviewer to test the app.

  • Contact Information: Details of anyone who is able to answer reviewer’s questions.

  • Screenshots: You need at least these:

    1. 5.5": 1242 x 2208
    2. 6.5": 1242 x 2688
    3. 12.9" iPad Pro: 2048 x 2732
    4. We recommend Chrome’s developer tools to take screenshots. [See section below]
  1. Save and submit for review.
  • Advertising id: Not used.

  • Encryption:

    1. Does your app use encryption? Select Yes even if your app only uses the standard encryption within Apple’s operating system. Yes
    2. Does your app qualify for any of the exemptions provided in Category 5, Part 2 of the U.S. Export Administration Regulations? Yes
    3. Details: Our app uses both HTTPS, and local encryption of the user’s data. We do not use end-to-end encryption. It is our view that this qualifies for the exemptions mentioned. However, this is not legal advice. If a developer customer makes their own submission, they need to take full responsibility here to follow the legal requirements.

Review Considerations

Unless the app has a very good use case for background location tracking, we need to disable it in the JourneyApps container. This is a common point for rejection.

The Apple reviewer will actually test the app, and reject it if there are major bugs.

The reviewer has sign into an app matching the description. For security purposes, it is recommended to use the actual app it’s intended for, but create a dedicated staging or production environment for this. This isolates the data nicely from the rest, and allows setting up basic test data specifically for the review process.

If the app is using SSO, this can get more complicated. In this case, actual test SSO credentials will have to be provided.

TestFlight

TestFlight is available once the App Store entry above has been created (but does not have to be submitted for review yet).

TestFlight can be used for beta testing, before or in addition to publishing on the private App Store. For users on the Apple developer account, no review is required to get the build into TestFlight. For external users, a beta review must be performed first. This review process is a lot less strict, but can also take 2 days on average. You then get an invite link that can be sent to any user. These builds are installed via the TestFlight app, and expire after 3 months (then needs a version bump and new upload). The app may still be usable on the device after the 3 months, but do not rely on this.

To use TestFlight, you need to manage the users that have access under App Store Connect → TestFlight → App Store Connect Users

For internal users, the iCloud account doesn’t have to be on the device. The process works like this:

  1. Add Apple developer user to the app’s TestFlight users.
  2. The user will receive an email with an invite link.
  3. Opening the link gives a redemption code.
  4. The redemption code can be used on any one device with TestFlight installed.

Using Chrome for screenshots

  1. Enroll to your app using https://testing.onjourneyapps.com
  2. Open developer tools, and toggle the device toolbar.

  1. In the device dropdown menu, select Edit and add a device like this:

  1. Now use the following three devices for screenshots:
  • iPhone 6/7/8 Plus (built-in) - 1242 x 2208
  • iPhone 11 Pro Max (the custom one created in step 3 above) - 1242 x 2688
  • iPad Pro (built-in) - 2048 x 2732
  1. Note that these resolutions are the resolutions in Chrome, multiplied by the device pixel ratio.
  2. Use Chrome’s screenshot tool - available in the more menu. If you use the built-in OS screenshot tool, the device pixel ratio will not be applied, and the resolution will be incorrect.

We have recently been seeing some responses from Apple regarding NFC references in our builds. If Apple responds asking about the NFC use, but you explicitly did not enable the “NFC Tag Reading” during the Identifier creation, the following can be shared with Apple:

It is true that our framework references NFC. However, without the NFC entitlement, there is no code that actually triggers NFC or is able to do so.