GASとスプレッドシートで作るメルマガ配信システム 全体設計とデータ構造の解説

,
メルマガ配信を導入しようとすると、外部の有料配信スタンドやSaaSの月額コスト、専用エディタの操作研修、顧客情報の外部委託に伴うセキュリティ管理など、多くのハードルが存在します。

日頃から社内で使い慣れている「Googleスプレッドシート」「Googleドキュメント」「Gmail」「Google Apps Script (GAS)」で連携させれば、ランニングコスト0円で、誰でもリッチテキスト編集が可能な高機能メルマガ配信システムを内製できます。

本連載(全5回)では、即時配信・日時指定の予約配信・本文プレビュー・リアルタイム進捗管理・300件送信時の360秒タイムアウト対策(約80秒への短縮)まで備えた本格的なメルマガ配信システムの仕組みとコードを詳しく解説します。

第1回となる本記事では、システム全体のアーキテクチャと、信頼性の高い配信を支えるスプレッドシートのデータ構造設計を解説します。

メルマガ配信システム 全体アーキテクチャとデータ構造

図1-1:メルマガ配信システムの全体アーキテクチャとデータ連携フロー

Google Workspaceを活用したメルマガ配信システムの利点

自前でメルマガ配信システムを構築する際、Google Workspaceのエコシステムを活用することには以下の大きなメリットがあります。

項目 特徴とメリット
インフラ費用0円 Google Workspaceの通常アカウント権限内で完結するため、専用サーバーや外部配信スタンドの月額固定費が一切発生しません。
ノーコードでの原稿作成 メール本文はGoogleドキュメント上で直接編集できます。太字、文字色、見出し、画像の貼り付けなど、非エンジニアの担当者でも直感的に作成できます。
顧客データの一元管理 宛先リストはGoogleスプレッドシートで管理されるため、社内の他システムやフォーム回答からの自動連携が容易で、外部サービスへの顧客情報エクスポート・インポートの手間と個人情報流出リスクを回避できます。
高いメール到達率 自社の正規Google Workspaceドメイン(GmailApp)から直接送信されるため、SPFやDKIMの署名が自動で付与され、迷惑メール判定を受けるリスクを低く抑えられます。

システム全体アーキテクチャと連携構造

本システムは、ユーザーが操作するブラウザ画面(フロントエンド)、業務ロジックを担うGAS(バックエンド)、そしてGoogle各種サービス(Docs、Sheets、Gmail)の3層構造で構成されています。

全体の処理の流れは以下の通りです:

全体連携の流れ

  1. 管理画面の起動:ユーザーがWebアプリケーションのURLにアクセスすると、GASの「doGet」経由で管理画面(index.html)が表示されます。
  2. 宛先と原稿の自動同期:画面表示時にスプレッドシートの「送信リスト」から宛先一覧が読み込まれ、Googleドキュメントの最新本文プレビューが取得されます。
  3. 配信操作の実行:ユーザーが件名を入力し、「即時配信」または「配信予約(日時指定)」を選択して配信ボタンを押します。
  4. メール送信と安全除外:バックエンドのGASがドキュメントをHTML化・画像インライン化し、送信直前にスプレッドシートで配信停止宛先を二重除外した上でGmailApp経由で送信します。
  5. 履歴と日時の自動更新:送信成功した宛先の「前回メール配信」日時を更新し、「配信履歴」シートに詳細結果を1行追記します。

宛先を管理する「送信リスト」シートの設計仕様

メルマガの宛先マスタとなる「送信リスト」シートは、シンプルながら確実な追跡ができるように設計します。

項目名 説明・用途
A列 (1) 顧客ID 文字列 顧客を一意に識別する管理コード(例: CUST-001)
B列 (2) 氏名 文字列 顧客のお名前。メール本文内の差し込みタグ「{{Recipient.Name}}」に置換されます。
C列 (3) メールアドレス 文字列 送信先メールアドレス。ユニークキーとして突合に使用されます。
D列 (4) 前回メール配信 日時 送信が成功した際にシステムが自動書き込みする実行日時(yyyy/MM/dd HH:mm:ss)。
E列 (5) 配信状況 文字列 「配信」または「停止」のプルダウン。停止の顧客は自動で除外されます。

多様なステータス入力を統一する安全な正規化ロジック

スプレッドシートを手動で運用していると、E列(配信状況)に「○ / ×」「1 / 0」「TRUE / FALSE」「配信可 / 配信停止」など、人によって異なる表記が入力されるケースが多発します。

誤送信や除外漏れを防ぐため、バックエンド側で厳格な正規化関数を用意し、どのような表記であってもプログラム上では確実に「配信」または「停止」の2値として扱います。

/**
 * 配信状況の文字列を「配信」または「停止」に厳格に正規化する関数
 */
function getDeliveryStatus(value) {
  if (value === null || value === undefined) return DELIVERY_STATUS_ACTIVE;
  var str = String(value).trim().toLowerCase();
  
  // 停止を表す代表的な表記をすべて網羅
  if (
    str === '停止' ||
    str === 'false' ||
    str === '0' ||
    str === '×' ||
    str === 'x' ||
    str === 'no' ||
    str === 'stop' ||
    str === '無効' ||
    str === '配信停止' ||
    str === '除外'
  ) {
    return DELIVERY_STATUS_STOPPED;
  }
  
  return DELIVERY_STATUS_ACTIVE;
}

/**
 * 配信停止状態かどうかを判定する関数
 */
function isUnsubscribed(value) {
  return getDeliveryStatus(value) === DELIVERY_STATUS_STOPPED;
}

さらに、スプレッドシートが開かれた際やWebアプリ起動時に「ensureDeliveryStatusColumn()」を実行し、E列のヘッダーを「配信状況」に自動統一した上で、スプレッドシートのデータ入力規則(プルダウン: 配信 / 停止)を自動付与する仕組みを組み込みます。

配信結果と予約を記録する「配信履歴」シートの設計仕様

配信履歴シートは、いつ・誰が・どのような件名で何通送信したのかを正確に記録し、万が一の障害時に原因を究明するためのブラックボックスとしての役割を果たします。

列番号 項目名 設定例 役割
1列目 配信ID IMM_1726000000 即時(IMM_)または予約(RES_)の一意識別子
2列目 作成日時 2026/09/15 10:00:00 配信指示・予約登録が行われた日時
3列目 配信日時 2026/09/15 18:00:00 実際に配信された日時、または配信予定の日時
4列目 配信種別 即時配信 / 配信予約 配信タイプ
5列目 ステータス 送信済み / 待機中 現在の状態(送信済み、待機中、キャンセル、配信失敗)
6列目 件名 新着お知らせのご案内 配信したメールの件名
7列目 宛先数 300 対象となった宛先総件数
8列目 宛先データ(JSON) [{“email”:”…”}] 予約配信時にバックグラウンド実行へ引き渡す宛先JSONデータ
9列目 トリガーID 9876543210 予約時に発行されたGASタイマートリガーの一意識別ID(取消時に使用)
10列目 送信結果 成功: 300件 / 失敗: 0件 送信完了メッセージ、エラー理由、失敗メールアドレス一覧

環境変更に強いグローバル定数の管理設計

システムを運用するにあたり、コード内の随所にシート名やIDを直接書き込む(ハードコードする)と、シート名変更やテスト環境への移行時に予期せぬ不具合を招きます。

「Code.gs」の先頭にグローバル定数を集約し、汎用的に変更できる構成にしておきます。

/**
 * ==============================================================================
 * メルマガ配信システム グローバル設定定数
 * ==============================================================================
 */

// スプレッドシート設定
var SPREADSHEET_ID = 'YOUR_SPREADSHEET_ID_HERE'; // 顧客リスト管理スプレッドシートのID
var SHEET_NAME = '送信リスト';                     // 宛先一覧シート名
var SCHEDULE_SHEET_NAME = '配信履歴';             // 履歴・予約管理シート名

// Googleドキュメント設定
var TEMPLATE_DOC_ID = 'YOUR_TEMPLATE_DOC_ID_HERE'; // メール本文原稿ドキュメントのID

// メール送信設定
var SENDER_NAME = '自社メルマガ事務局';              // メールの差出人表示名
var FROM_EMAIL = 'newsletter@example.com';          // 送信元メールアドレス(厳格照合対象)
var PLAIN_FALLBACK = 'お使いのメールソフトはHTMLメールに対応しておりません。';

// ステータス定数
var DELIVERY_STATUS_ACTIVE = '配信';
var DELIVERY_STATUS_STOPPED = '停止';

var SCHEDULE_STATUS_PENDING = '待機中';
var SCHEDULE_STATUS_COMPLETED = '送信済み';
var SCHEDULE_STATUS_CANCELLED = 'キャンセル';
var SCHEDULE_STATUS_FAILED = '配信失敗';

var DELIVERY_TYPE_IMMEDIATE = '即時配信';
var DELIVERY_TYPE_SCHEDULED = '配信予約';

var HISTORY_MAX_ROWS = 10; // 管理画面に表示する最新履歴件数

宛先リストを一括取得するGASバックエンド処理

スプレッドシートから宛先データを取得する際は、セルを1行ずつ読み込むのではなく、「getDataRange().getValues()」で全データをメモリ上に一括ロードして配列処理を行うのがGASの定石です。

/**
 * 送信リストシートから宛先一覧を取得し、正規化して返す
 */
function getRecipients() {
  try {
    var ss = SpreadsheetApp.openById(SPREADSHEET_ID);
    var sheet = ss.getSheetByName(SHEET_NAME);
    if (!sheet) {
      throw new Error('シート「' + SHEET_NAME + '」が見つかりません。');
    }

    var data = sheet.getDataRange().getValues();
    if (data.length <= 1) {
      return []; // ヘッダーのみの場合は空配列
    }

    var recipients = [];
    // 2行目(インデックス1)から走査
    for (var i = 1; i < data.length; i++) {
      var row = data[i];
      var ownerId = String(row[0] || '').trim();
      var name = String(row[1] || '').trim();
      var email = String(row[2] || '').trim();
      var lastSent = row[3];
      var statusRaw = row[4];

      // メールアドレスが不正な行はスキップ
      if (!email || email.indexOf('@') === -1) {
        continue;
      }

      var formattedLastSent = '';
      if (lastSent instanceof Date) {
        formattedLastSent = Utilities.formatDate(lastSent, Session.getScriptTimeZone(), 'yyyy/MM/dd HH:mm');
      } else if (lastSent) {
        formattedLastSent = String(lastSent);
      }

      recipients.push({
        ownerId: ownerId,
        name: name,
        email: email,
        lastSent: formattedLastSent,
        status: getDeliveryStatus(statusRaw),
        unsubscribed: isUnsubscribed(statusRaw)
      });
    }

    return recipients;
  } catch (error) {
    Logger.log('getRecipients エラー: ' + error.toString());
    throw new Error('宛先リストの取得に失敗しました: ' + error.message);
  }
}

まとめと次回予告

第1回のまとめ

  • Google Workspace(スプレッドシート・Docs・Gmail)を組み合わせることで、完全自前の安全なメルマガ配信基盤を構築可能。
  • 「送信リスト」は顧客ID・氏名・メールアドレス・前回配信日時・配信状況の5列構成で無駄のない追跡を実現。
  • 手動入力のブレに対応するため、バックエンドで配信ステータスを「配信」「停止」に自動正規化。
  • 「配信履歴」シートにより、即時配信と予約配信の全実行ステータスとエラーログを永続化。

次回(第2回)は、「GoogleドキュメントをHTMLメルマガに変換する仕組み 本文生成とインライン画像埋め込みの解説」をお届けします。
非エンジニアでも直感的に作成できるDocs原稿を、GmailのMIMEマルチパート仕様(CID埋め込みインライン画像)に自動変換する高度なサニタイズ&画像抽出ロジックを詳しく解説します。


eguchi.netをもっと見る

購読すると最新の投稿がメールで送信されます。