新しい SaaS のウェイトリスト

新しい SaaS のウェイトリスト2 分で読めます

App Store 公開前のモバイルアプリ用ウェイトリストページの作り方

使っているスマホを尋ね、iPhone のテスターを TestFlight に送り、Play のクローズドテストを埋める、iOS・Android アプリ向けの公開前ウェイトリストです。

Preet SutharIkiform 創業者

アプリのランディングページに、使っているスマホと早期テストを希望するかを尋ねるウェイトリストフォームを置きます。iPhone のテスターは Webhook と App Store Connect API で TestFlight のグループに送り、Android のテスターはエクスポートから Google Play のクローズドテストに登録します。公開日には、各グループにそれぞれのストアへのリンクをメールで送ります。

アプリがストアに並ぶまでは、案内できるインストールボタンがありません。自分のランディングページにウェイトリストを置けば、それまで関心をつなぎ止められるうえ、ストアにはできないもう 1 つの役割も果たせます。公開前に必要なテスターを見つけることです。

モバイルアプリならではの違い

  • ストアが 2 つ、リストも 2 つ。 公開のメールは App Store か Google Play にリンクし、テストの招待は TestFlight か Play のクローズドテストで送ります。適切なほうを送るには、各人のプラットフォームを知る必要があります。
  • テスターはストアのアカウントと一致している必要があります。 TestFlight はどのアドレスにも招待メールを送ります。Play のクローズドテストはリストにある Google アカウントしか受け入れないので、仕事用のアドレスで登録した Android のテスターはビルドをインストールできません。
  • Google Play では公開前にテスターが必要な場合があります。 2023 年 11 月 13 日以降に作成された個人のデベロッパーアカウントは、本番に公開する前に、14 日間連続でオプトインし続けるテスターが 12 人以上いるクローズドテストを実施する必要があります。その 12 人を集める場所がウェイトリストです。
  • ストアには独自の公開前ツールがあります。 ウェイトリストの代わりではなく、併用する価値があります。下の比較を見てください。
ItemApp Store の予約注文、Play の事前登録自分のウェイトリスト
ストアの掲載情報が必要はい、承認済みで準備完了のものいいえ。ランディングページの初日から使えます
得られるもの件数と、リリース時のストアからの通知または自動ダウンロード各人のメールアドレス、プラットフォーム、回答
テスターを招待できるかいいえはい、メールで TestFlight や Play のクローズドテストに
制限新しい個人アカウントでは、テストの要件を満たすまで Play の事前登録は使えません公開のメールは自分で送ります

アプリのウェイトリストのフィールド

メールアドレス欄の意味が変わるので、まずプラットフォームを尋ねます。機種は任意で、どの端末でテストすべきかがわかります。ベータのチェックボックスで、テストしたい人と公開のメールだけほしい人を分けます。

プレビュー

Ikiform のバリデーターをブラウザーで実行します。何も送信されません。

Android の場合は、Google Play にログインしている Google アカウントを入力してください。

お使いのスマホは?
platform のラジオボタンは必須なので、どの登録も対応可能なリストに入ります。定義をフォームの Fields タブに貼り付けてください。

フォーム

このフォームは、どのランディングページでもプレーンな HTML として動きます。スクリプトが行うのは 2 つの小さなことだけです。訪問者のブラウザーからプラットフォームを事前に選択し、Google アカウントの注意書きを Android のユーザーにだけ表示します。

index.html
<form id="app-waitlist" action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
  <fieldset>
    <legend>Which phone do you use?</legend>
    <label><input type="radio" name="platform" value="iphone" required> iPhone</label>
    <label><input type="radio" name="platform" value="android"> Android</label>
  </fieldset>

  <label for="email">Email</label>
  <input id="email" name="email" type="email" autocomplete="email" inputmode="email" required>
  <p id="email-hint" hidden>Use the Google account you sign in to Google Play with.</p>

  <label for="first_name">First name</label>
  <input id="first_name" name="first_name" autocomplete="given-name" maxlength="60">

  <label for="device">Phone model</label>
  <input id="device" name="device" maxlength="80" placeholder="iPhone 15, Pixel 8">

  <label><input type="checkbox" name="beta" value="true"> Invite me to test the app before launch</label>
  <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden>
  <button>Join the waitlist</button>
</form>

<script>
  const form = document.getElementById("app-waitlist");
  const agent = navigator.userAgent;
  const guess = /iPhone|iPad/.test(agent) ? "iphone" : /Android/.test(agent) ? "android" : null;
  if (guess) form.querySelector(`[name="platform"][value="${guess}"]`).checked = true;
  const hint = () => {
    document.getElementById("email-hint").hidden = form.platform.value !== "android";
  };
  form.addEventListener("change", hint);
  hint();
</script>
  • 推測はラジオボタンを事前に選択するだけです。変更もでき、スクリプトがなければ何も選択されないので、required によって必ず選んでもらえます。
  • inputmode="email" と autocomplete="email" は、ここでは他のどこよりも重要です。アプリのランディングページの訪問者はほとんどがスマホからだからです。
  • Redirect after submit(送信後のリダイレクト)に、プラットフォームごとに次に何が起きるかを説明するサンクスページを設定します。フレームワーク版のフォームはメインのガイドを参照し、これらのフィールドを追加してください。

テスターをそれぞれのストアに振り分ける

iPhone と Android のテスターが各ストアのベータに届くまでiPhoneAndroid登録platform はiphone、beta はtrueWebhookの中継署名を検証App StoreConnect のAPIベータテスターを追加TestFlightの招待Apple がメール送信登録platform はandroid、betaは trueCSVエクスポートプラットフォームで絞り込みPlayConsoleクローズドテストのメールリストオプトインリンク自分で送信署名付き毎週iPhone と Android のテスターが各ストアのベータに届くまでiPhoneAndroid登録platform は iphone、beta は trueWebhook の中継署名を検証App Store Connect の APIベータテスターを追加TestFlight の招待Apple がメール送信登録platform は android、beta は trueCSV エクスポートプラットフォームで絞り込みPlay Consoleクローズドテストのメールリストオプトインリンク自分で送信署名付き毎週
  1. iPhone のユーザーがベータのチェックボックスにチェックを入れると、署名付き Webhook が送られます。
  2. 中継サーバーがそれを検証し、App Store Connect API を呼び出してアドレスを TestFlight のグループに追加します。
  3. グループで利用できるビルドができると、Apple が TestFlight の招待メールを送ります。
  4. Android のテスターも同じ方法で集め、CSV でエクスポートしてプラットフォームで絞り込みます。
  5. Google アカウントを Play Console のクローズドテストのメールリストに貼り付けます。
  6. メールツールから、テストのオプトインリンクをメールで送ります。
Apple にはテスター用の API がありますが、Google Play のクローズドテストは Play Console か Google グループで管理するので、Android 側は毎週のエクスポートになります。

iPhone: TestFlight にテスターを追加する

App Store Connect で外部テストグループを作成し、Users and Access › Integrations で App Manager か Admin のロールを持つ API キーを作成します。次に、Ikiform の submission.created Webhook を受け取る中継サーバーを追加します。ここでは Next.js のルートハンドラーで、Apple のトークンの署名に jose を使っています。fetch があるサーバーならどこでも動きます。

app/api/testflight/route.ts
import { verifyWebhook, WebhookVerificationError } from "@ikiform/sdk/webhooks";
import { importPKCS8, SignJWT } from "jose";

async function appStoreToken() {
  const key = await importPKCS8(process.env.ASC_PRIVATE_KEY!, "ES256");
  return new SignJWT({})
    .setProtectedHeader({ alg: "ES256", kid: process.env.ASC_KEY_ID!, typ: "JWT" })
    .setIssuer(process.env.ASC_ISSUER_ID!)
    .setAudience("appstoreconnect-v1")
    .setIssuedAt()
    .setExpirationTime("15m")
    .sign(key);
}

export async function POST(request: Request) {
  const payload = await request.text();
  try {
    const event = await verifyWebhook({ payload, headers: request.headers, secret: process.env.IKIFORM_WEBHOOK_SECRET! });
    if (event.type !== "submission.created") return new Response(null, { status: 204 });
    const { email, first_name, platform, beta } = event.data.fields;
    if (platform !== "iphone" || beta !== true) return new Response(null, { status: 204 });

    const response = await fetch("https://api.appstoreconnect.apple.com/v1/betaTesters", {
      method: "POST",
      headers: { authorization: `Bearer ${await appStoreToken()}`, "content-type": "application/json" },
      body: JSON.stringify({
        data: {
          type: "betaTesters",
          attributes: { email: String(email), firstName: first_name ? String(first_name) : undefined },
          relationships: { betaGroups: { data: [{ type: "betaGroups", id: process.env.ASC_BETA_GROUP_ID! }] } },
        },
      }),
    });
    if (response.ok || response.status === 409) return new Response(null, { status: 204 });
    return new Response(`App Store Connect answered ${response.status}`, { status: 502 });
  } catch (error) {
    if (error instanceof WebhookVerificationError) return new Response(error.reason, { status: 400 });
    throw error;
  }
}
  • ASC_PRIVATE_KEY は Apple から受け取る .p8 ファイルの中身で、改行も含みます。ASC_KEY_ID と ASC_ISSUER_ID は同じページにあります。
  • Apple のトークンの有効期間は最大 20 分なので、中継サーバーは配信ごとに新しいトークンに署名します。
  • 409 は Apple が変更を競合として拒否したことを意味し、多くはそのアドレスがすでにテスターである場合です。中継サーバーはこれを完了として扱うので、招待が届かないテスターがいたら App Store Connect でグループを確認してください。それ以外の失敗には 502 を返すので、Ikiform が配信をリトライします。
  • TestFlight では、アプリごとに最大 10,000 人の外部テスターを追加できます。外部グループに最初に渡すビルドは Beta App Review を通る必要があり、各ビルドは 90 日で期限切れになります。

Ikiform の Webhooks に中継サーバーの URL を追加し、whsec_ のシークレットを IKIFORM_WEBHOOK_SECRET にコピーします。1 つのワークスペースにエンドポイントは 3 つまで登録できるので、メールツールに送る中継サーバーと並べて動かせます。

Android: クローズドテストを埋める

Google Play の公開 API はクローズドテストのテスターを個々のアドレスではなく Google グループで管理するので、こちらは毎週のエクスポートで対応します。Export CSV でリストをエクスポートし、スプレッドシートで platform = android かつ beta = true に絞り込んで、Play Console の Test and release › Testing › Closed testing › Manage track › Testers にあるクローズドテストのメールリストにメールアドレスを追加します。そのあと、メールツールからその人たちにテストのオプトインリンクを送ります。

12 人のテスター要件を満たすつもりなら、12 人より多く招待してください。オプトアウトした人や一度もオプトインしなかった人は数に入らず、14 日間ずっと 12 人がオプトインしている必要があります。

Ikiform はフォームに入力した人にはメールを送りません。通知メールが届くのはあなたとチームだけなので、自動返信、確認メール、ダブルオプトインはありません。それらは Webhook をきっかけにメールツールから送ってください。TestFlight は独自の招待を送るので、iPhone のテスターには Apple から連絡が届きます。Android のテスターに連絡するのはあなただけです。

ローンチ日

リスト全体をエクスポートし、platform で分けます。それぞれのグループに、ストアのリンクを 1 つだけ載せたメールを 1 通送ります。iPhone を選んだ人に Play のリンクは不要ですし、バッジを 2 つ並べたメールは小さな画面ではタップされにくくなります。device を集めていたなら、送る前に、登録者が挙げた機種とクラッシュレポートを照らし合わせておきましょう。

動作を確認する

  1. フォームの Allowed domains に、自分のサイトのドメインと、開発中は localhost を追加します。空にしておくと、どこからの送信も受け付けます。
  2. スマホでページを開きます。正しいプラットフォームが事前に選択され、Android では Google アカウントの注意書きが表示されます。
  3. iPhone のテスターとして送信します。送信データの platform は iphone、beta は true になります。
  4. Webhooks で中継サーバーのエンドポイントを開きます。配信は succeeded と表示され、アドレスが TestFlight のグループに載っています。
  5. Android のテスターとして送信し、CSV をエクスポートして、platform を android にするとその行が絞り込まれることを確認します。
  6. ハニーポットに値を入れて _gotcha=bot で送信します。成功の応答は返りますが、送信データはスパムとして保存され、メールも Webhook も送られません。

ハマりどころ

問題対処法
中継サーバーへの配信が 502 で失敗し、Apple が 401 を返したトークンが間違っています。キー ID、発行者 ID を確認し、環境変数の秘密鍵の改行が保たれているか確かめてください。
テスターは追加されたが TestFlight のメールが届かないグループにまだビルドがないか、最初のビルドが Beta App Review 中です。グループでビルドが利用可能になると招待が送られます。
Android のテスターがオプトインリンクを開けないリストにあるアドレスが、スマホでログインしている Google アカウントと違います。そのアカウントでもう一度登録してもらってください。
422 validation_failed送信がフォームのフィールド定義に合っていません。error.fields に各フィールドと理由が示されます。入力欄の name をフィールド ID に合わせてください。
送信データはダッシュボードにあるのに、メールも Webhook も届かないスパムとして保存されています。スパムは何も送りません。Spam フィルターで開いて理由を確認し、スパムではないとマークすると、メールと Webhook が送られます。

よくある質問

手順で詰まったときや、あなたの状況に合うガイドがほしいときはメールでどうぞ: hello@ikiform.com