iOSアプリのアップデートをリリースするたびに、アプリのLP(ランディングページ)サイトのリリースノートを手動で更新していませんか。 そして、「App Storeのアプリリリース審査が通ったら、あとでLPも更新しよう」と思いながら、ついつい後回しにしてしまい、気づいたら数バージョン分たまっている…そんなことはないでしょうか。

そこで、App Storeの審査が完了したタイミングで、LPのリリースノートページが自動で更新される仕組みを作ってみました。 一度設定してしまえば、あとは何もしなくてOK。 コピペしなくても、タイミングを待たなくても、勝手にLPが更新されていきます。自動化って最高ですね

私が最近リリースした個人制作アプリ「Tsumugu」は、日本語と英語に対応しているため、App Storeから日英両方のリリースノートを取得して自動更新してみることにしました。

この記事で実現すること

全体の流れは下記の形になります。

  1. App Store審査完了
  2. AppleからGmail(Google Workspace)にて「Ready for Distribution」メールを受信
  3. Google Apps Scriptがメールを検知
  4. GitHub Actionsをトリガー
  5. App Store APIから最新リリースノートを取得
  6. JSONファイルを更新してコミット
  7. Vercelで自動デプロイ

この流れのキモは、「App Store APIから最新リリースノートを取得」することなので、それ以外の部分が異なる構成になっても活用できます。

今回のLPサイトで利用したサービス

  • ソースコード管理:GitHub & GitHub Actions
  • フレームワーク:Astro
  • ホスティング:Vercel
  • メールの検知:Google Apps Script

実装手順

1. iTunes Search APIからデータを取得する

App Storeの情報は、iTunes Search APIで取得できます。このAPIは認証不要の公開APIで、誰でも使えます
自身のアプリのAPP_IDを指定して下記のURLを叩くと、アプリの情報を取得できます。

https://itunes.apple.com/lookup?id={APP_ID}&country=jp

レスポンスからは下記の情報が取得できます。

  • version – バージョン番号
  • releaseNotes – リリースノート
  • currentVersionReleaseDate – リリース日

ただ、このAPIには制限があって、現在公開中のバージョンの情報しか取得できません
過去のバージョン履歴を取得できないことから、取得したデータをリポジトリ内のJSONファイルに蓄積していく方式にしました。

※App Store Connectの「アプリのバージョン履歴」にはバージョン番号と提出日は残っているのですが、リリースノート本文は保存されていません。正確な文言を復元することはできないので、自動で蓄積されていく仕組みを早めに導入しておくのがおすすめです。

過去のリリースノートをラクに用意する方法

今回、APIで取得できなかった過去のリリースノートに関しては、Gitのコミット履歴からバージョン変更のタイミングと変更内容を追跡して、Claude Codeに推測で作成してもらいました。
Gitでは、以下のようなコマンドで、これまでのバージョン変更の履歴を確認できます。この履歴を元にJSONファイルに過去の履歴を追加するよう、プロンプトを書いて実行しました。

git log -p --all -S "MARKETING_VERSION" -- "*.pbxproj"

2. リリースノート取得スクリプトの作成

まずは /scripts/fetch-release-notes.mjs を作成し、リリースノートを取得できるようにします。 取得したデータはJSONファイルに保存します。

#!/usr/bin/env node

import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const DATA_FILE = path.join(__dirname, "../src/data/release-notes.json");
const APP_ID = "YOUR_APP_ID"; // App Store ConnectまたはURLから確認

async function fetchFromAppStore(country) {
  const url = `https://itunes.apple.com/lookup?id=${APP_ID}&country=${country}`;
  const res = await fetch(url);
  const data = await res.json();

  if (!data.results || data.results.length === 0) {
    throw new Error(`No results found for country: ${country}`);
  }

  const app = data.results[0];
  return {
    version: app.version,
    releaseNotes: app.releaseNotes || "",
    releaseDate: app.currentVersionReleaseDate,
  };
}

function loadExistingData() {
  if (!fs.existsSync(DATA_FILE)) {
    return { releases: [] };
  }
  const content = fs.readFileSync(DATA_FILE, "utf-8");
  return JSON.parse(content);
}

function saveData(data) {
  const dir = path.dirname(DATA_FILE);
  if (!fs.existsSync(dir)) {
    fs.mkdirSync(dir, { recursive: true });
  }
  fs.writeFileSync(DATA_FILE, JSON.stringify(data, null, 2) + "\n");
}

async function main() {
  console.log("Fetching release notes from App Store...");

  // 日本とUSのストアから取得(多言語対応)
  const [ja, en] = await Promise.all([
    fetchFromAppStore("jp"),
    fetchFromAppStore("us"),
  ]);

  console.log(`Latest version: ${ja.version}`);

  const data = loadExistingData();

  // 同じバージョンが既に存在するかチェック
  const existingRelease = data.releases.find((r) => r.version === ja.version);

  if (existingRelease) {
    console.log(`Version ${ja.version} already exists. Skipping.`);
    return;
  }

  // 新しいリリースを先頭に追加
  const newRelease = {
    version: ja.version,
    releaseDate: ja.releaseDate,
    notes: {
      ja: ja.releaseNotes,
      en: en.releaseNotes,
    },
  };

  data.releases.unshift(newRelease);
  saveData(data);
  console.log(`Added version ${ja.version} to release notes.`);
}

main().catch((err) => {
  console.error("Error:", err.message);
  process.exit(1);
});

バージョン番号で重複チェックをしているところがポイントです。複数回実行しても、同じバージョンが重複して追加されないようにしました。

3. データファイルの構造

src/data/release-notes.json は以下のような形式で保存されます。

{
  "releases": [
    {
      "version": "1.5.0",
      "releaseDate": "2026-02-01T16:36:55Z",
      "notes": {
        "ja": "写真選択画面から直接カメラを起動できるようになりました。\n\n- カメラボタンを追加\n- タップするだけで撮影可能",
        "en": "You can now take photos directly from the photo picker.\n\n- Added camera button\n- Tap to capture instantly"
      }
    },
    {
      "version": "1.4.0",
      "releaseDate": "2026-01-28T12:00:00Z",
      "notes": {
        "ja": "iCloud同期機能を追加しました。",
        "en": "Added iCloud sync functionality."
      }
    }
  ]
}

新しいバージョンが配列の先頭に追加されていくので、ページ表示時は配列をそのまま順番に表示すれば、新しい順になります。

4. リリースノートページの作成

日本語ページ src/pages/releases.astro を作成します。
リリースノートが記載されているJSONファイルを元に、コンテンツを生成しています。 今回は、リリースノートをほぼそのままテキストとして流し込んでいますが、HTML構造が気になる方はテキストをパースしてHTML要素に変換するなどの処理を行ってください。

---
import Layout from '../layouts/Layout.astro'
import Footer from '../components/Footer.astro'
import releaseData from '../data/release-notes.json'

const lang = 'ja'

function formatDate(dateString: string): string {
  const date = new Date(dateString)
  return date.toLocaleDateString('ja-JP', {
    year: 'numeric',
    month: 'long',
    day: 'numeric',
  })
}

function formatNotes(notes: string): string[] {
  return notes.split('\n').filter(line => line.trim() !== '')
}
---

<Layout title="リリースノート - アプリ名" lang={lang}>
  <main>
    <article>
      <h1>リリースノート</h1>
      <div>
        {releaseData.releases.map((release, index) => (
          <section>
            <div class="flex items-baseline gap-4 mb-4">
              <h2>
                v{release.version}
              </h2>
              <time datetime={release.releaseDate}>
                {formatDate(release.releaseDate)}
              </time>
            </div>
            <div>
              {formatNotes(release.notes.ja).map(line => (
                <p class={line.startsWith('-') ? 'pl-4' : ''}>{line}</p>
              ))}
            </div>
          </section>
        ))}
      </div>
    </article>
  </main>
</Layout>

英語ページは src/pages/en/releases.astro に同様の内容で作成して、release.notes.ja を release.notes.en に、日付フォーマットを en-US に変更すればOKです。

CSSでスタイリングすると、下記のような見た目のページができます。

白い背景に黒い文字で構成された「リリースノート」の画面。バージョン1.5.2(2026年2月8日)の内容として、iOS 18の色付き・モノクロ表示モードへのウィジェット対応や写真表示の最適化、背景・バッジの調整が記載されている。その下にはバージョン1.5.1(2026年2月2日)の内容として、画像シェア時にモーダルが閉じてしまうバグの修正が記載されている。

5. GitHub Actionsの設定

次に、上記のスクリプトを実行するGitHub Actionsを作成します。
下記の内容で、.github/workflows/update-release-notes.yml を作成します。

name: Update Release Notes

on:
  # 毎日 9:00 JST (0:00 UTC) にチェック(バックアップ)
  schedule:
    - cron: "0 0 * * *"
  # 手動実行
  workflow_dispatch:
  # 外部からのwebhookトリガー(GAS等)
  repository_dispatch:
    types: [app-released]

jobs:
  update:
    runs-on: ubuntu-latest

    permissions:
      contents: write

    defaults:
      run:
        working-directory: lp #ご自身のリポジトリ内容に応じて変更してください

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20"

      - name: Fetch release notes
        run: node scripts/fetch-release-notes.mjs

      - name: Check for changes
        id: changes
        run: |
          if git diff --quiet src/data/release-notes.json; then
            echo "changed=false" >> $GITHUB_OUTPUT
          else
            echo "changed=true" >> $GITHUB_OUTPUT
          fi

      - name: Commit and push
        if: steps.changes.outputs.changed == 'true'
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add src/data/release-notes.json
          git commit -m "Update release notes from App Store"
          git push

トリガーは3つ設定しています。

  • schedule – 毎日定時実行(バックアップ用)
  • workflow_dispatch – 手動実行
  • repository_dispatch – 外部からのwebhook(GASから呼び出す用)

変更がない場合はコミットをスキップし、空コミットを作らないようにしています。

6. Google Apps Scriptの設定

App Storeの審査完了時にAppleから届く「Ready for Distribution」メールをトリガーにして、GitHub Actionsを起動します。
「リアルタイム更新じゃなくても、一日一回更新すれば充分、定時実行のみで良い」という場合には、ここから先のGASの設定は不要で、これまでの設定で問題なく動きます。
ここからは、「アプリがリリースされたら更新したい」を実現するためのステップになります。

GitHub Personal Access Tokenの作成

  1. GitHubにログインして、Settings → Developer settings → Personal access tokens → Fine-grained tokensへ移動
  2. Generate new tokenをクリック
  3. Repository accessで対象リポジトリを選択
  4. Permissionsで「Contents: Read and write」を付与
  5. トークンを生成してコピー

GASプロジェクトの作成

  1. Google Driveで新規 → その他 → Google Apps Scriptを選択
  2. 以下のコードを貼り付ける
function checkAppleEmail() {
  // Appleからの審査完了メールを絞り込むための条件
  const query =
    'from:no_reply@email.apple.com subject:"Ready for Distribution" is:unread';
  const threads = GmailApp.search(query);

  if (threads.length === 0) {
    return;
  }

  const url = "https://api.github.com/repos/YOUR_USERNAME/YOUR_REPO/dispatches"; // GitHubユーザー&リポジトリ名を置き換える
  const token = "YOUR_GITHUB_TOKEN"; // トークンを置き換える

  const options = {
    method: "post",
    headers: {
      Authorization: "Bearer " + token,
      Accept: "application/vnd.github+json",
      "Content-Type": "application/json",
    },
    payload: JSON.stringify({
      event_type: "app-released",
    }),
    muteHttpExceptions: true,
  };

  const response = UrlFetchApp.fetch(url, options);
  console.log("GitHub API Response: " + response.getResponseCode());

  // 処理済みのメールを既読にする
  threads.forEach((thread) => thread.markRead());
}

このスクリプトでは、メールアカウントにて、指定した条件のメールを絞り込み、条件に一致すればGitHub Actionsをトリガーしています。 
YOUR_USERNAME/YOUR_REPO と YOUR_GITHUB_TOKEN は実際の値に置き換えてください。また、複数のアプリをリリースしている場合は、subject:"Ready for Distribution"の部分をご自身のアプリの名称が入ったReady for Distributionメールの件名に置き換えると、該当のアプリがリリースされた場合にのみGitHub Actionsをトリガーできます。

トリガーの設定

上記のGASを定期的に実行する設定を行います。

  1. 左メニューの「トリガー」(時計アイコン)をクリック
  2. 「トリガーを追加」をクリック
  3. 以下のように設定
    ・実行する関数: checkAppleEmail
    ・イベントのソース: 時間主導型
    ・時間ベースのトリガーのタイプ: 分ベースのタイマー
    ・間隔: 15分おき(お好みでご変更ください)
  4. 保存
Google Apps Scriptのトリガー設定画面のスクリーンショット。左側にサイドバーメニュー、右下に「トリガーを追加」ボタン、中央に「LPリリース更新のトリガーを編集」というポップアップウィンドウが表示されている。ウィンドウ内では、実行する関数として「checkAppleEmail」が選択され、イベントのソースは「時間主導型」、トリガーのタイプは「分ベースのタイマー」、間隔は「15分おき」に設定されている。画面の各所には赤い丸で囲まれた「1」「2」「3」の番号が付記されている。

初回実行時に、Gmailへのアクセス許可を求められるので、許可します。

GASで15分おきにタイマー起動するなら、GitHub Actionsで15分おきに定期実行すればいいのでは…?という気もしてきますが、GitHub Actionsの実行には、プランに応じた上限時間が設定されています。できるだけGitHub Actionsの実行時間を節約しつつ、アプリのリリースと近い時間にLPを更新するには、GASを使うと良いです。

まとめ

これで、アプリLP更新フローが完全に自動化されました。 アプリをリリースするだけで、LPのリリースノートが勝手に最新化されます。 英語と日本語の両サイトにリリースノートの内容をコピペして更新するのは地味に面倒でもあったため、自動化できてラクになりました。 毎日9時の定期実行もバックアップとして設定しているので、万が一GASやメールのトリガーが失敗しても、翌日には反映されます。

そもそもアプリのLPにリリースノートは必要なのか、という点について疑問に思う方もいるかもしれません。 アプリは「リリースして終わり、メンテしない」と言うことも考えられますが、そんなアプリを自分で使うのは、ちょっと不安ではないでしょうか。 リリースノートページがあって、それが順調に更新されていることで、「長くメンテを続けているアプリだな」「不具合を修正してくれるのだな」という安心感をユーザーに与えることができます。

また、私は2021年から細々と個人開発を続けているアプリ「Kanau」がありますが、リリースノートを眺めていると、いろんな思い出が蘇ってきて、それもまたいいものだと感じます。

薄紫色の背景に白文字で記載された、アプリケーション「kanau」の更新履歴(リリースノート)のスクリーンショット。画面左側には縦書きで「『つくる人』のための計算メモ帳 kanau」とあり、中央にはバージョン1.0.0から1.0.7までの修正内容や機能追加が日付(2021/4/7〜2021/5/4)とともにリスト形式で表示されている。右上には黒い背景に白文字で「Download」と書かれたボタンがあり、右端にはホームとハートのアイコンが配置されている。
公開からコツコツと更新し続けているアプリ「Kanau」のリリースノートページ

ユーザーのためにも、自分の思い出にもなるリリースノート。ぜひあなたのアプリLPでもラクラク更新してみてください💫