GA4を毎日チェックしたいけど、ログインが面倒…という方に向けて、Google Apps Script(GAS)を使ってGA4のアクセスレポートを毎朝自動でメール配信する仕組みを解説します。

前日比・直近7日間の平均比で アクセス数が急落したときだけ件名が変わる「異常値アラート」機能 も実装するので、gtag の設置漏れや広告停止にも早期に気づける実用的なスクリプトになっています。


    この記事でできること

    • GA4 のアクティブユーザー数・PV数を毎朝自動でメール配信
    • 前日比 / 直近7日平均比が −40% 以下になったとき、件名に ⚠️【要確認】 を付けて通知
    • CONFIG を1か所書き換えるだけで複数サイトに対応

    完成イメージ

    正常時の件名

    ✅ GA4 デイリーレポート (2026-04-27)

    アクセス急落時の件名

    ⚠️ 【要確認】GA4 アクセス異常検知 (2026-04-27)

    メール本文(抜粋)

    GA4 デイリーレポート
    2026-04-27 09:07 配信
    
    ◯ 昨日(2026-04-26)の実績
      アクティブユーザー数:234
      ページビュー数   :612
    
    ◯ 前日比(一昨日 198 人との比較)
      ▲+36 人(+18.2%)
    
    ◯ 直近7日平均比(平均 210 人との比較)
      ▲+24 人(+11.4%)

    セットアップ手順

    1. GCPプロジェクトを作成する

    Google Cloud Console を開き、新しいプロジェクトを作成します。作成後、 プロジェクト番号 を控えておいてください。

    2. Google Analytics Data API を有効にする

    Cloud Console → APIとサービスライブラリGoogle Analytics Data API を検索し、有効にします。

    3. OAuth 同意画面を設定する

    Cloud Console → Google Auth Platform (または「APIとサービス」→「OAuth同意画面」)を開きます。

    • アプリ名は任意(例:ga4-daily-report
    • サポートメール / デベロッパー連絡先は自分のメールアドレス
    • 対象(テストユーザー)に、GASを実行するGoogleアカウントを追加

    「テスト中」ステータスのままで個人利用は問題ありません。

    4. Google Apps Script のプロジェクトを作成する

    Google Apps Script を開き、 新しいプロジェクト を作成します。

    左メニューの歯車アイコン( プロジェクトの設定 )→ GCPのプロジェクト番号 に手順1で控えた番号を入力します。

    5. appsscript.json を設定する

    プロジェクトの設定から「appsscript.json マニフェストファイルをエディタで表示する」をオンにします。

    エディタに appsscript.json が表示されたら、内容を以下に置き換えます。

    {
      "timeZone": "Asia/Tokyo",
      "exceptionLogging": "STACKDRIVER",
      "runtimeVersion": "V8",
      "oauthScopes": [
        "https://www.googleapis.com/auth/analytics.readonly",
        "https://www.googleapis.com/auth/script.send_mail",
        "https://www.googleapis.com/auth/script.external_request"
      ]
    }

    oauthScopes を明示しないと analytics.readonly スコープが付与されず、GA4 API 呼び出し時に ACCESS_TOKEN_SCOPE_INSUFFICIENT エラーが発生します。先にこのファイルを保存してから次に進んでください。

    6. Code.gs にスクリプトを貼り付ける

    デフォルトの コード.gs を開き、内容を以下のスクリプトに置き換えます。

    /* ============================================================
     * GA4 デイリーレポート(異常値アラート付き)
     * ======================࿐༶⊰⟡⊱༶⊰⟡⊱༶⊰⟡⊱༶❀༶⊰⟡⊱༶⊰⟡⊱༶⊰⟡⊱༶༄ */
    
    // ============================================================
    // ★ 設定エリア(ここだけ書き換えてください)
    // ============================================================
    const CONFIG = {
      // 送信先メールアドレス(複数の場合はカンマ区切り: 'a@b.com,c@d.com')
      // 空欄にすると GAS 実行アカウントのメールに届きます
      REPORT_TO: 'your@email.com',
    
      // GA4 プロパティ設定(サイトを追加する場合はここに足すだけ)
      GA_PROPERTIES: [
        { label: 'サイト名', propertyId: '000000000' }
      ],
    
      // 異常値アラートの閾値(前日比でこの割合以上減ったら警告)
      // 0.4 = 40%減でアラート
      ALERT_THRESHOLD: 0.4,
    
      // 直近何日間の平均をベースラインにするか
      BASELINE_DAYS: 7,
    };
    // ============================================================
    
    
    function sendDailyReport() {
      const tz = 'Asia/Tokyo';
      const now = new Date();
    
      const dYesterday    = formatDate_(addDays_(now, -1), tz);
      const dDayBefore    = formatDate_(addDays_(now, -2), tz);
      const dBaselineFrom = formatDate_(addDays_(now, -(CONFIG.BASELINE_DAYS + 1)), tz);
      const dBaselineTo   = formatDate_(addDays_(now, -2), tz);
    
      const results = CONFIG.GA_PROPERTIES.map(p => {
        const yesterday  = gaReport_(p.propertyId, dYesterday, dYesterday);
        const dayBefore  = gaReport_(p.propertyId, dDayBefore, dDayBefore);
        const baseline   = gaReport_(p.propertyId, dBaselineFrom, dBaselineTo);
        const baselineAvg = {
          users: Math.round(baseline.users / CONFIG.BASELINE_DAYS),
          pv:    Math.round(baseline.pv    / CONFIG.BASELINE_DAYS),
        };
    
        const diffVsDayBefore = calcDiff_(yesterday.users, dayBefore.users);
        const diffVsBaseline  = calcDiff_(yesterday.users, baselineAvg.users);
    
        const alert =
          diffVsDayBefore.rate <= -CONFIG.ALERT_THRESHOLD ||
          diffVsBaseline.rate  <= -CONFIG.ALERT_THRESHOLD;
    
        return { label: p.label, yesterday, dayBefore, baselineAvg,
                 diffVsDayBefore, diffVsBaseline, alert };
      });
    
      const hasAlert = results.some(r => r.alert);
      const subject  = hasAlert
        ? `⚠️ 【要確認】GA4 アクセス異常検知 (${dYesterday})`
        : `✅ GA4 デイリーレポート (${dYesterday})`;
    
      MailApp.sendEmail(getReportTo_(), subject, buildMailBody_(results, dYesterday, tz, now));
    }
    
    
    /* ===== GA4 API ===== */
    
    function gaReport_(propertyId, startDate, endDate) {
      const url = `https://analyticsdata.googleapis.com/v1beta/properties/${propertyId}:runReport`;
      const payload = {
        dateRanges: [{ startDate, endDate }],
        metrics: [{ name: 'activeUsers' }, { name: 'screenPageViews' }],
      };
      const token = ScriptApp.getOAuthToken();
      const resp  = UrlFetchApp.fetch(url, {
        method: 'post', contentType: 'application/json',
        headers: { Authorization: `Bearer ${token}` },
        payload: JSON.stringify(payload), muteHttpExceptions: true,
      });
      const code = resp.getResponseCode();
      if (code < 200 || code >= 300) {
        throw new Error(`GA4 API エラー (${code}): ${resp.getContentText()}`);
      }
      const row = JSON.parse(resp.getContentText())?.rows?.[0]?.metricValues || [];
      return {
        users: row[0] ? Number(row[0].value) : 0,
        pv:    row[1] ? Number(row[1].value) : 0,
      };
    }
    
    
    /* ===== メール本文 ===== */
    
    function buildMailBody_(results, dYesterday, tz, now) {
      const dt    = Utilities.formatDate(now, tz, 'yyyy-MM-dd HH:mm');
      const lines = [`GA4 デイリーレポート`, `${dt} 配信`, ''];
    
      results.forEach(r => {
        const alertMark = r.alert ? '⚠️ ' : '';
        lines.push('────────────────────────');
        lines.push(`■ ${alertMark}${r.label}`);
        lines.push('────────────────────────', '');
        lines.push(`◯ 昨日(${dYesterday})の実績`);
        lines.push(`  アクティブユーザー数:${fmtInt_(r.yesterday.users)}`);
        lines.push(`  ページビュー数   :${fmtInt_(r.yesterday.pv)}`, '');
        lines.push(`◯ 前日比(一昨日 ${fmtInt_(r.dayBefore.users)} 人との比較)`);
        lines.push(`  ${diffRateLine_(r.diffVsDayBefore, CONFIG.ALERT_THRESHOLD)}`, '');
        lines.push(`◯ 直近${CONFIG.BASELINE_DAYS}日平均比(平均 ${fmtInt_(r.baselineAvg.users)} 人との比較)`);
        lines.push(`  ${diffRateLine_(r.diffVsBaseline, CONFIG.ALERT_THRESHOLD)}`, '');
        if (r.alert) {
          lines.push(`  ⚠️  アクセス数が基準値より ${Math.round(CONFIG.ALERT_THRESHOLD * 100)}% 以上減少しています。`);
          lines.push(`      gtag / GTM の設置状況、広告配信状況をご確認ください。`, '');
        }
      });
    
      lines.push('────────────────────────');
      lines.push(`※ ▲ はプラス、▼ はマイナスを示します`);
      lines.push(`※ ⚠️ アラートは前日比・直近${CONFIG.BASELINE_DAYS}日平均比のいずれかが -${Math.round(CONFIG.ALERT_THRESHOLD * 100)}% 以下の場合に発火します`);
      lines.push(`※ 本メールは自動送信されています`);
      lines.push('────────────────────────');
      return lines.join('\n');
    }
    
    function diffRateLine_(diff, threshold) {
      const pct      = (diff.rate * 100).toFixed(1);
      const mark     = diff.value >= 0 ? `▲+${fmtInt_(diff.value)}` : `▼${fmtInt_(Math.abs(diff.value))}`;
      const rateMark = diff.rate >= 0 ? `+${pct}%` : `${pct}%`;
      const warn     = diff.rate <= -threshold ? ' ← ⚠️ 要確認' : '';
      return `${mark} 人(${rateMark})${warn}`;
    }
    
    
    /* ===== ユーティリティ ===== */
    
    function getReportTo_() {
      const to = (CONFIG.REPORT_TO || '').trim();
      return to || Session.getEffectiveUser().getEmail();
    }
    function calcDiff_(current, base) {
      return { value: current - base, rate: base > 0 ? (current - base) / base : 0 };
    }
    function formatDate_(date, tz) {
      return Utilities.formatDate(date, tz, 'yyyy-MM-dd');
    }
    function addDays_(date, n) {
      const d = new Date(date);
      d.setDate(d.getDate() + n);
      return d;
    }
    function fmtInt_(n) {
      return (Number(n) || 0).toLocaleString('ja-JP');
    }

    7. CONFIG を書き換える

    スクリプト冒頭の CONFIG を自分の環境に合わせて編集します。

    項目 説明
    REPORT_TO 送信先メールアドレス。空欄にすると実行アカウントに届く
    GA_PROPERTIES label(表示名)と propertyId(GA4プロパティID)を設定
    ALERT_THRESHOLD アラート閾値。0.4 = 40%減で発火
    BASELINE_DAYS ベースラインとする日数(デフォルト7日)

    GA4 プロパティIDの確認方法: GA4 管理画面 → 左下「管理」→「プロパティの設定」に表示される9〜10桁の数字。

    複数サイトの追加:

    GA_PROPERTIES: [
      { label: 'サイトA', propertyId: '111111111' },
      { label: 'サイトB', propertyId: '222222222' },
      { label: 'サイトC', propertyId: '333333333' },
    ],

    1通のメールに主要なサイトがまとめて届きます。1サイトでもアラートが出れば件名が ⚠️ になります。

    8. 動作確認する

    上部バーの関数選択から sendDailyReport を選び、「実行」ボタンを押します。

    初回は OAuth 認証ダイアログが表示されます。「このアプリは Google で確認されていません」と出た場合は「詳細」→「(プロジェクト名)に移動」→「許可」と進んでください。

    メールが届けば動作確認完了です。

    9. トリガーを設定する

    Apps Script 左メニューの時計アイコン「 トリガー 」→「 トリガーを追加 」から設定します。

    項目 設定値
    実行する関数 sendDailyReport
    イベントのソース 時間主導型
    タイマーの種類 日付ベースのタイマー
    時刻 午前8時〜9時(お好みで)
    エラー通知設定 今すぐ通知を受け取る

    エラー通知は「今すぐ」にしておいてください。この仕組みは異常を検知するためのものなので、監視役自身が黙って停止している状態が最も困ります。

    また、トリガー一覧(左メニューの時計アイコン)はプロジェクトごとに見てください。Apps Script トップの「マイトリガー」は全プロジェクト横断で、プロジェクト名が同じだと見分けがつきません。区別できるのは「前回の実行」列だけです。

    保存するとトリガー一覧に追加され、翌日から自動送信されます。


    よくあるエラーと対処法

    ACCESS_TOKEN_SCOPE_INSUFFICIENT(403エラー)

    analytics.readonly スコープが付与されていないときに出ます。原因は2パターンあります。

    パターン1:oauthScopes を追加する前に認証してしまった

    古い権限がキャッシュされたままなので、appsscript.json を直しただけでは解消しません。GAS エディタ左メニューの「概要」→「権限を取り消す」をクリックして再度実行し、OAuth ダイアログで改めて許可してください。「権限を取り消す」が表示されない場合は、myaccount.google.com/permissions から該当のアプリを削除してから再実行します。

    パターン2:プロジェクトを作り直した

    appsscript.jsonコード.gs とは別のファイルです。新しいプロジェクトに コード.gs を貼り直しても、oauthScopes は空のデフォルトのままなので復活しません。GCPプロジェクト番号の紐付けも外れています。

    この場合は、必ず次の順番で進めてください。

    1. プロジェクトの設定 → GCPのプロジェクト番号を入力 →「プロジェクトを設定」
    2. appsscript.jsonoauthScopes を追加して保存(手順5)
    3. sendDailyReport を実行 → OAuth ダイアログで許可
    4. トリガーを再登録(手順9)

    1 を実行した時点で古い認証は自動的に取り消されるため、「権限を取り消す」の手動操作は不要です。逆に 2 を飛ばして 3 を実行すると、また既定スコープのまま認証が固まって同じ403に戻ります。

    なお、Apps Script プロジェクトの実体は Google Drive 上のファイルです。消えた場合は Drive のゴミ箱(保持30日)を確認してください。復元できれば、トリガーもGCP紐付けもそのまま戻ります。日頃から コード.gsappsscript.json をセットでローカル保存しておくと、復旧が数分で済みます。

    Bandwidth quota exceeded

    原因: 短時間に何度も手動実行してAPIの呼び出し回数上限に達した。

    対処: 20〜30分待ってから再実行する。トリガーによる自動実行は1日1回のみなので、通常運用では発生しない。

    メールが届かない

    原因: トリガーの設定後すぐには実行されない。次の指定時刻まで待つ必要がある。

    確認方法: トリガー一覧の「前回の実行」列に日時が表示されていれば正常に動いている。「エラー率」が0%でなければ実行ログを確認する。


    アラート閾値の目安

    ALERT_THRESHOLD 値 意味 向いているサイト
    0.3 30%減で発火(敏感) 流量が安定していて日々の変動が少ないサイト
    0.4 40%減で発火(推奨) 多くのサイトで誤検知と見逃しのバランスが取れる
    0.5 50%減で発火(鈍感) 週末に大きく落ちる傾向があるサイト

    実際に1〜2週間分のデータで何日アラートが出るか確認してから閾値を調整するのがおすすめです。


    まとめ

    GASでGA4レポートを自動配信する仕組みは、設定ファイル(CONFIG)を1か所書き換えるだけで複数サイトに対応でき、維持費もかかりません。

    特に複数のWordPressサイトをGTMで管理している場合、大幅改修の際にタグが設置漏れになってもこの仕組みがあればすぐに気づけます。「前日比−40%以下でアラート」という単純なルールですが、実際の計測障害や広告停止を早期発見するのに十分実用的です。