How we launched Metacast mobile app on App Store and Play Store

The entire process from getting the DUNS number to handling rejections by Apple and Google.

Author's picture

Ilya Bezdelev

Co-founder / CEOCompany


We have finally launched Metacast in Apple App Store and Google Play Store. It was our first ever mobile app launch, and it was a massive undertaking! We thought we'd document the process for posterity. Maybe it'd be helpful to other developers.

In this post, we'll walk you through the non-obvious parts of the process, the decisions we made, and things we would've done differently.

Developer accounts

To distribute mobile apps, you have to have a developer account with Apple and Google. Developer accounts give you access to Apple's App Store Connect and Google's Play Console where you'll publish your app, create in-app purchases, see download stats, etc.

Developer accounts come with a fee:

  • Apple charges $99/year for individual and business accounts
  • Google only charges a $25 one-off registration fee

Organization vs. individual developer accounts

When you create a developer account, you must choose whether you're a company or an individual.

We are a Delaware C-corp, but we briefly considered starting with an individual account, because it's easier to create. The idea was that we'd get started quickly and then move over to a new organization account.

Ultimately, we chose to start with an organization account, because saving time now meant we'd have to do all the necessary steps later anyway. That felt like a waste, so we decided to eat the frog early on and forget about it.

D-U-N-S number

Note: DUNS is only relevant to companies. It is not required for developers launching apps as individuals.

In order to create an organization's developer account, you must have a D-U-N-S number. You've probably never heard of it. Neither had we until we started Metacast.

What is a DUNS number?

DUNS number is an identifier issued by an entity called Dun & Bradstreet that Apple and Google use to verify whether you're a legitimate business.

From D&B website:

The Dun & Bradstreet D-U-N-S® Number is a unique nine-digit identifier for businesses that is associated with a business’s Live Business Identity which may help evaluate potential partners, seek new contracts, apply for loans, and so much more.

Our understanding is that D&B has an interface with governments around the world and is able to verify whether your business is real. Working with D&B helps companies like Apple and Google outsource the verification of your business.

To apply for DUNS, simply follow the steps listed on their website on the Claim Your Free D-U-N-S Number page.

When to apply for DUNS?

We applied for DUNS over a year ago when we first started working on the app, because it was required for the Apple Developer Account. It wasn't required by Google at the time, but it is required now.

D&B assigned us a DUNS number within a few business days with the following note:

Confirmed through the Florida Secretary of State that Metacast Inc. is located at [REDACTED] with the telephone number [REDACTED]. Therefore, a DUNS number has been generated for the business.

Check if all the information in your DUNS record is correct. My name was misspelled and we had to go through a lengthy process of opening a ticket, waiting for the response, etc. It was not fun.

Apparently, the rules differ for "regular" DUNS numbers and DUNS numbers created for the mobile development purpose. You cannot change the DUNS information yourself for the latter. To make changes, you must open a ticket using this link, which I obtained from the D&B support after several unsuccessful attempts to modify the information myself.

DUNS and the "trader" status to comply with DSA

Before the launch, we learned that to charge European users, we had to declare ourselves a "trader" to comply with the Digital Services Act (DSA).

The "trader" requirement boils down to two relevant points:

  • Your address and phone number will be public on the app stores in Europe. Ouch.
  • Both Apple and Google will use your company's address and phone number from the DUNS record.

Changing the address in DUNS

We initially used a home address and a personal phone number to register the company, which obviously, we didn't want to become public. So, we registered a business address and a phone number, and requested a change in our DUNS record.

The most difficult part came later when we needed to update Apple and Google with our new contact information.

Changing the information in Apple's systems was easy. We went through the trader verification process, submitted the required documents, and were done. There was a slight delay in the process because we had to both upload the documents and reply to Apple's email.

Make sure to read instructions carefully. Some of the big tech processes are from the stone age, but ultimately, it's your responsibility to follow them.

Google Play was a different story... Google sources the address and the phone number from DUNS, but here's the catch — they don't sync the changes with D&B, so we were stuck with the outdated contact information.

After a week of fruitless back-and-forth with Google's support teams for Google Play Console and Google Payments (you enter DUNS when you create a Payments Profile), we were beyond frustrated.

In the end, this is what they recommended:

To resolve the issue, I recommend creating a new payments profile for your developer account. This will automatically generate the DUNS information for the new payments profile.

We ended up creating a new payments profile and dread the moment we need to change our address or phone number again. Considering that every time you create a Payments Profile, you need to verify your bank account, which also takes a few days, doing it last minute can add extra lead time to your launch schedule.

In retrospect, we should've registered a business address and a business phone when we set up the company.

Developer account & bank verification with Google

We can't remember the process of verifying the identity with Apple. Perhaps, it was done automatically because Apple already had our details from Apple IDs. Or maybe it was so straightforward and quick that our minds didn't even register it.

Google's process is fresh in our memory, because it was recent.

Identity verification

When Google asked for a verification, we uploaded our company's registration documents and my driver's license. The license was rejected because the picture had a glare.

Your identity could not be verified for the following reasons:
Government Issued Photo ID: There was reflection or glare on the image, obstructing required information.

It took a lot of effort and fiddling with lighting to take a picture of my Florida license without a glare, because it has multiple reflecting layers. Sometimes, even TSA scanners can't read it. In retrospect, I should've uploaded my passport instead.

Bank account verification

To verify the banking information, Google used a banking equivalent of a one-time code. They sent us a small payment ($0.55), and we needed to verify the amount in the Play Console after receiving the money.

The process was straightforward, but we'd prefer it be done quicker with tools like Plaid that can verify bank accounts instantly.

Testing in-app purchases before launch

Before the launch, the only part of the app's functionality that wasn't thoroughly tested was the in-app subscription purchases.

We'd had Metacast in beta for almost a year and offered the full functionality for free to our beta users. Payments were behind a feature flag and were only accessible to our team.

There's a principal difference in how Apple and Google handle in-app payments in beta:

  • In Apple TestFlight, beta users can go through the flow of purchasing a subscription — they are told that they won't be charged and asked to enter their Apple ID password. When they do so, the subscription is activated for 15 minutes, after which the paywall comes back. It's impossible to test subscriptions with real money.
  • On Android, beta users can pay for a subscription and their credit card will be charged through the Play Store. It's really easy to test.

On Android, we added a friend into the internal user group and asked her to buy a subscription, so we could see that the workflow works with real money. On Apple, we just had to trust that it would work.

Release stages

Prior to the launch, we had three stages in App Store Connect and Play Console:

  • Internal beta — the stage that only has team members. On both Apple and Google platforms, new builds are available for testing for the internal group immediately.
  • Closed beta — the stage that has early beta testers. Each user is added manually, we know most of them personally, and we have emails for all of them.
  • Open beta — the stage that anyone with a link can join to test the app. We don't have emails for those users and don't know who they are. On iOS, they use the app via TestFlight. On Android, they download the beta app through the Play Store.

We think of these stages as a pipeline. Each build first goes to the internal stage, then closed beta, then open beta. Launching in app stores meant we'd need to add a new stage for production and sunset the beta program for external users.

More on that later.

Submission to Apple App Store

To get your app into the App Store, Apple has to review and approve the app. The submission is done in App Store Connect and is generally straightforward.

In this post, we'll talk about the things that made us stumble.

Screenshots for older iPhones

You have the option to submit screenshots in a variety of screen sizes, of which most are optional. Screenshots for the 6.9" and 5.5" iPhones are required.

The latest 5.5" iPhone was the iPhone 8 with a Touch ID button at the bottom, so we had to put together two sets of screenshots.

We took 6.9" screenshots on a physical iPhone 14 Pro device and 5.5" screenshots in the iOS Simulator. The iPhone 8 simulator required an older version of iOS 16.4 (which took an extra 8 Gb of disk space).

To create the final imagery, we used Figma and Canva.

iPad screenshots

We build with Flutter and our app has a fairly good native iPad support, but there are some edge cases that we chose to ignore for now. However, since iPad was one of the build targets in Xcode, iPad screenshots were required in App Store Connect. We did not want to be subjected to testing on an iPad by Apple, because they'd surely discover the issues and ask us to fix those.

We disabled the iPad as a target, and App Store Connect stopped asking for the screenshots. As of now, Metacast runs on the iPad in a compatibility mode.

Rejected by Apple

After submitting the app for review, Apple rejected it a few times. In retrospect, we could've addressed all issues in one go, but we didn't due to the lack of experience with the Apple review process.

Rejection reason 1: In-app subscriptions not submitted

Metacast comes with a premium subscription. We created a subscription in App Store Connect and diligently filled in all the details, but Apple rejected the submission.

Guideline 2.1 - Performance - App Completeness

We are still unable to complete the review of your app because one or more of your in-app purchase products have not been submitted for review.

Next Steps
To resolve this issue, please be sure to take action and submit your in-app purchases and upload a new binary in App Store Connect so we can proceed with our review.

The issue was that we had to mark the subscription ready for review in the Subscriptions section of the App Store Connect and also mark it for submission in the final app review form. It took us a few rejected attempts to figure this out in the App Store Connect UI.

Rejection reason 2: Introductory offer

To celebrate the iOS launch, we are offering the premium subscription at 50% off for the first year. It is set up as an introductory offer in App Store Connect. However, the reviewer didn't see that and was confused by the $24.99 price in the app (which didn't match the $49.99 regular subscription price).

Guideline 2.1 - Performance - App Completeness

We are unable to complete the review of your app because one or more of your in-app purchase products have not been submitted for review.
Specifically, 24.99USD subscription.

We responded with the details about the offer and screenshots from App Store Connect. The issue was resolved.

This rejection was the most unexpected. Apple said we didn't have the links that we in fact did have.

Guideline 3.1.2 - Business - Payments - Subscriptions
3.1.2(c) Subscription Information Before asking a customer to subscribe, you should clearly describe what the user will get for the price. How many issues per month? How much cloud storage? What kind of access to your service? Ensure you clearly communicate the requirements described in Schedule 2 of the Apple Developer Program License Agreement.

Issue Description
...
The app metadata must also include functional links to the privacy policy and Terms of Use (EULA).
Next Steps
Update the app's binary to include the following required information:
– A functional link to the Terms of Use (EULA)
– A functional link to the privacy policy\

Metacast had links to ToS and Privacy Policy both in the subscriptions screen and in Settings, but Apple was testing the app on a small device (in a simulator on an iPad, left screenshot) and they didn't see them.

Since we didn't test every single screen on a small device, we never discovered this issue ourselves. On recent iPhones (middle screenshot), the links are above the fold.

Even though a user can scroll to see the links, it was not obvious in the UI. We updated the screen to make sure that even on small screens users can see a part of the disclaimer. When users see that there's more content at the bottom, they can scroll to see it all.

Rejection reason 4: Missing Terms of Service and Privacy Policy metadata

The ToS rejection had an additional action.

Update the app's metadata to include the following required information:
– A functional link to the Terms of Use (EULA). If you are using the standard Apple Terms of Use (EULA), include a link to the Terms of Use in the App Description. If you are using a custom EULA, add it in App Store Connect.
– A functional link to the privacy policy

Since we use custom Terms of Service (not Apple's EULA), we had to paste the raw ToS text into a field in App Store Connect and add ToS and Privacy Policy URLs to the app's description.

The latter requirement is weird, because Apple doesn't turn those URLs into clickable links. Neither can you select and copy them in the App Store on the iPhone.

Approved by Apple!

We submitted the app on September 16 and got it approved on September 20. Apple's feedback was mostly procedural and cosmetic, so it was easy to address. The turnaround time between iterations was 1 day.

We chose to manually release the app after approval (rather than have it auto-published by Apple). Once we passed Apple's review, we soft-launched in the App Store right away. It took a few hours before the app started to appear in searches.

Submission to Google Play Store

The Google Play Store submission was uneventful. We filled in a bunch of questionnaires, like privacy, data safety, etc., uploaded images, and hit "Submit."

After the submission, the app got approved by Google very quickly. Based on what followed next, our hypothesis is that Google didn't thoroughly review our app before it hit the store.

Google Play Store violations

Within a few days after the approval, we received three violation notice emails from Google.

Violation 1: Undisclosed collection of Device IDs

Version code 441: Policy Declaration - Data Safety Section: Device Or Other IDs Data Type - Device Or Other IDs (some common examples may include Advertising ID, Android ID, IMEI, BSSID, MAC address)

We were under the impression that we do not collect any of the data mentioned above. As we dug deeper, we learned that Firebase Installation ID (collected automatically by Firebase) is considered a Device ID.

We addressed this violation by resubmitting the Data Safety form where we checked the box for Device ID. The violation was resolved.

Violation 2: Unattributed user testimonials

Your app contains content that doesn’t comply with the Metadata policy. Specifically:
The app's full and/or short description includes unattributed user testimonials or more than five attributed quotes/testimonials
Your app may face additional enforcement actions, if you do not resolve this issue by August 13, 2024.

Initially we thought the issue was the number of testimonials. We had six testimonials in our app listing's copy. We removed one of them and resubmitted the app. The violation didn't get resolved.

We decided not to risk it and removed all testimonials from the copy. Still, the violation remained. Then, we finally noticed that one of the images also had testimonials. We removed the image and this resolved the violation.

Violation 3: Subscription copy is not clear

Issue found: Violation of Subscriptions policy
Your app does not comply with the Subscriptions policy.
Your offer does not clearly and accurately describe the terms of your trial offer or introductory pricing, including when a free trial will convert to a paid subscription, how much the paid subscription will cost, and that a user can cancel if they do not want to convert to a paid subscription.

We didn't really agree with the violation, because we worked really hard to make the copy on the subscriptions screen as clear as possible. While we were considering options, the violation got automatically resolved.

Big company processes are a mystery.

When you get rejected...

When we got the first violation from Google, I had a bit of a "sky is falling, we're going to be removed from the Play Store" reaction. I dropped everything I was doing and looked at it. My state of mind was that of David threatened by Goliath.

Once we resolved the violations, I realized that they are not a that big of a deal, if you're not trying to game the system in the first place.

When we got a rejection notice from Apple, it was disappointing, but it felt fair. The stakes were low, because we didn't have the app in the App Store yet.

There's a drastic difference between Apple's and Google's approaches.

  • Apple is a gatekeeper. They won't let you in until you meet the requirements. They front-load the review process, but it's (hopefully) an easy ride after that. Also, there's a human on the other end that you can respond to.
  • Google lets you in and blindsides later on. It was not fun to receive a notice with the threat of a potential removal from the Play Store. Google's process has a human in the loop, but you "appeal" a violation. It doesn't feel like a collaboration.

We'd take Apple's process over Google's any day.

Ending the beta

We decided to end the beta after we launched in production. That turned out to be more difficult than we expected.

Tooling overview

Let's start with the technical overview of our feature roll-out tooling.

  1. Feature toggling (a.k.a. feature flags) — we push to main and control who gets to access a new feature with a simple in-house feature toggling mechanism. The logic is simple — if the feature is enabled for the current user, show the feature; otherwise, don't. Feature flagging allows us to test features internally before enabling them for everyone.
  2. Versionarte — on rare occasions, we need our users to upgrade to the latest version of Metacast. Versionarte is a Flutter package that enables us to set the minimum required version. If a user has a version that's lower than the required one, they won't be able to run the app until they upgrade.
  3. Remote Config — we use Firebase Remote Config for feature flagging, minimum version, and other configuration that may change. Remote Config is where we define user groups that get to see a feature and toggle features on and off. Typically, once we toggle a feature, users see it within minutes.
  4. Automated CI/CD — we have multiple stages in both Apple and Google release pipelines: internal, closed beta, open beta, and production. We use Tramline to automatically build and roll out the app to Google and Apple. In case you're still uploading builds to Google and Apple consoles manually, try Tramline. It's a huge time saver!

Launching a major version manually is an important mechanism that lets you prep the marketing materials, prepare for supporting the app, etc.

For the App Store and Play Store launches, we've set up our production stage to be deployed manually. Rather than letting Apple/Google push the app to the store immediately after approval, they set it in a "waiting for distribution" mode and let us pull the trigger when we're ready.

Ending the iOS beta on TestFlight

Apple made it really simple for users to get off a beta. Users can simply reinstall the app from the App Store. The production app's installation overwrites the TestFlight installation and that's it. All user's data is preserved in the process.

Our main problem was that we weren't able to communicate to users to ask them to install the production app. The majority of our beta users used Metacast with an anonymous account or an account created using Apple's Private Relay that we can't yet send emails to.

We disabled the public TestFlight open beta link to stop new users from joining the beta. We also emailed all the users whose emails we had. For the rest, we devised a clever upgrade plan.

  • We created a "broken" build that would not let users use the app. When they open the app, they see a screen that forces them to install Metacast from the App Store.

  • We pushed the "broken" build to closed and open beta groups on TestFlight. Users who had automatic updates enabled would get the "broken" build auto-installed and be forced to reinstall the app from the App Store.
  • We created a regular (non-broken) build with the same version number and pushed directly to the App Store. Now, this is important, the "broken" build and the "normal" build both had the same version 1.9.
  • We set the minimum iOS version to 1.9.
    • Anyone in the beta would have to upgrade to 1.9 beta version and would get the "broken" build, after which they'd have to install the production app.
    • Production users on version below 1.9 would also have to update the app. Unfortunately, those production users were the collateral damage of the process. But this is why we did the "soft" launch, so we disrupt as few users as possible.
  • After a couple of days, we removed all closed and open beta users in App Store Connect.

From this moment on, we are no longer pushing new builds to open beta. The beta users who did not install the production app are stuck with a non-functional version of Metacast.

Ending the Android beta on Google Play Store

Ending the beta on Google was both simpler and more complicated.

Unlike Apple, Google made it very easy to join a beta. Google's beta apps live in the Play Store, users simply opt into a beta right in the Play Store on their phone. If there's no production app yet, users get the beta by default.

Unlike on iOS, ending the beta on Android is convoluted. Even if you stop the beta release tracks in the Play Console, users will continue to have access to the latest production build, but they will remain "beta users." The implication is that any reviews and ratings they leave will only be visible to you, not the general public on the Play Store.

There's no incentive for beta users to leave the beta. They can still use the app, pay for the app, and generally they don't care if their review is visible or not. To leave the beta, users must opt out of the beta (=do work), uninstall the app (=lose local data), and install it again (do more work). If I were a beta user, I'd just stay in beta unless I really wanted to support the developer with a review.

On iOS, we were able to force users off the beta. On Android, we could only encourage them. We emailed the users whose emails we had and replied to the test feedback they left for the beta in the Play Console.

After the app was in the Play Store, we bumped the minimum version to v1.1.1 that we currently had in production, so that everyone would be forced onto the production build.

Enabling the paywall

At the time of the public launch, our premium subscription and the paywall were behind a feature flag.

After we released the app to app stores, we removed the feature flag, and sent an email to all current users to inform them of the changes. The email thanked the users for their participation in the beta program, explained the Premium offering, and offered a discounted pricing for a limited period.

Dopamine hits FTW

When you launch a paid app, you want to have some dopamine hits whenever something good happens. We've set up a Slack integration for RevenueCat to receive a notification every time a user starts a free trial or converts to a premium subscriber. It also sends us notifications when people cancel their subscriptions. It does hurt a little bit but it's a reminder that monetization is a continuous work-in-progress.

Things we should've done differently (hindsight 20/20)

Next time we launch an app, we'll do a couple of things differently.

  • In-app messaging to users. In the earliest versions of the app we should've included a rudimentary mechanism to communicate to users. It could've been as simple as setting a parameter in Remote Config and displaying a crude screen with the message when the app is opened. That would've helped with the problem of being unable to communicate to anonymous users.
  • Parametrize the upgrade link. In the screen that prompts users to upgrade (when we set the minimum version), we hardcoded the TestFlight link. If the link were fetched from Remote Config, we could've replaced it with the App Store link remotely and avoid the upgrade churn we inflicted on our iOS beta users.

Tracking progress

Peter Drucker said it all in his, perhaps, most famous quote:

"If you can't measure it, you can't improve it."
— Peter Drucker

Before we launched, we made sure that we had all the data we need to track key metrics, such as revenue, adoption, usage, ratings, and retention. We funnel all our data to BigQuery, so we can easily run queries, join different data sources, and build dashboards.

We use the following integrations with BigQuery that come out of the box with the tools that we use:

We run quite a few queries in BigQuery directly, because everyone on our team is fluent in SQL, and we have a dashboard in the free Looker Studio analytics tool from Google.

Conclusion

Launching an app in an app store is a task that may feel daunting and complicated. Our rule of thumb followed the spirit of the famous Eisenhower's quote.

"In preparing for battle I have always found that plans are useless, but planning is indispensable."
— Dwight D. Eisenhower

We planned our work well, but we discovered most of what needed to be done as we were going through the process. We learned and adapted. Hopefully, our post will save you some churn.

Learn more

  • Watch/listen to our CTO Arnab and me talk about our launch process on episodes 59, 60, 61 and 62 of the Metacast: Behind the Scenes podcast
  • Join our r/metacastapp subreddit for more build-in-public posts
  • Subscribe to our newsletter
  • Follow me on LinkedIn, X or Threads