- ASCII.jp:パーソナライズ対応!Google Appsでメールマガジン配信システム (1/5)|Web制作をちょっと便利にするGoogle Apps Script入門
 - ASCII.jp:Googleフォームによる無料メルマガ配信システムの作り方 (1/3)|Web制作をちょっと便利にするGoogle Apps Script入門
 
これらのページに従って、メルマガ配信を作ってみましたの。そのなかで、次の点を変更しましたわ。
- 登録 ID は覚えやすいものにしたい → 長く覚えられないフォーム回答 ID ではなく、ユニークな乱整数を生成して使うようにした。
 - メルマガ作成はフォームから行う必要はない → ドキュメントのテンプレートを直接修正するようにした。
 - メルマガ本文の好きな場所に送信先の名前を入れたい → 変数 {name} を送信先の名前と置換するようにした。
 - メルマガ配信後、誰に送ったのかわかるようにしたい → スプレッドシートにメルマガ配信日時を追記するようにした。
 - メール送信残数を把握したい → 本日のメール送信残数をアラート表示するメニューをメルマガテンプレートドキュメントに追加
 - メルマガ解除フォームは不要 → 除外した。
 
今回、Google Apps Script (以下 GAS とも) を駆使して作っていった内容を残しておきますわ♪
目次
- 作るメルマガ登録・配信システムについて動きを整理します♪
- 人間が行う動き
- 登場人物
 - メルマガに登録
 - メルマガ登録者の確認、メルマガ送信対象者の確認
 - メルマガ執筆・送信・メール送信可能数の確認
 - なにかのイベント事のときは
 
 - システムの動き
- 登場ファイル
 - メルマガ登録
 - メルマガ送信
 - メール送信可能数を表示
 
 
 - 人間が行う動き
 - メルマガ登録・配信システムを作っていきます♪
- フォルダ・ファイル構成
 - メルマガシステムづくり
 - フォルダの作成
 - メルマガ登録フォームと回答スプレッドシートの作成
- フォームの作成
 - 回答スプレッドシートの作成と ID の取得
 - フォームのコードを書く
 - トリガーの設定
 - 動きの確認
 
 - メルマガテンプレートとなるドキュメントの作成
- ファイルと文章の作成
 - コードの作成
 - 動作の確認
 
 - もう一つのメルマガテンプレート「山」の作成
 
 - コードやシステムの課題
 - おわりに
 
1. 作るメルマガ登録・配信システムについて動きを整理します♪
まずは、操作する人間がどのような動きをするのか、それを受けてメルマガスタンドがどのように動くのか、を整理いたします。
1-1. 人間が行う動き
1-1-1. 登場人物
- 購読者: メルマガに登録し、メルマガを受け取って、読む人
 - 発行者: メルマガ登録者を管理し、メルマガを執筆し、発行する人
 
1-1-2. メルマガに登録
- 購読者はメルマガ登録フォームに名前、メールアドレス、購読ジャンルなどを入力する。
 - 購読者と発行者に登録ありがとうメールが届く。入力内容と、ランダムでユニークな登録 ID がメールには記載されている。
 
1-1-3. メルマガ登録者の確認、メルマガ送信対象者の確認
- 発行者はスプレッドシートを開いてメルマガ登録者を確認する。
 - 発行者はメルマガを送信したい対象者の、メルマガ送信チェック用のセル内容を(もし入っていたら)削除して、メルマガ送信対象とする。
 
1-1-4. メルマガ執筆・送信・メール送信可能数の確認
- 発行者は書きたいジャンルのメルマガテンプレート (Google ドキュメント) をコピーして開き、メルマガを執筆する。
 - 発行者はドキュメントのメニューから「メルマガ送信」をクリックし、メルマガを送信する。
 - ドキュメントのメニューから「メール送信残数」をクリックし、その日のメール送信可能残数を確認する。
 
1-1-5. なにかのイベント事のときは
- メルマガ登録時に届いたメールに記載された登録 ID を使って、簡易的な本人確認や抽選に使用する。
 
1-2. システムの動き
1-2-1. 登場ファイル
- Google フォーム: メルマガ登録の入力を受付け、スプレッドシートに回答を挿入する。
 - Google スプレッドシート: メルマガ登録者情報の記録、メルマガ送信済みか否かの記録を保持する。
 - Google ドキュメント: メルマガタイトル、本文を保持する。
 - Google Apps Script:
- ランダムでユニークな ID を生成してスプレッドシートに追記、登録受付メールの送信
 - メルマガ本文の変数 {name} をスプレッドシートの名前と置換、メルマガの送信
 - その日のメール送信可能残数を表示、
 
 
1-2-2. メルマガ登録
- フォームに入力されたデータをスプレッドシートに追記する。
 - フォームに設置したスクリプトが、スプレッドシートから今まで挿入された登録者 ID 列を取得し、ランダムでユニークな乱整数を生成する。
 - フォームに設置したスクリプトが、フォーム回答を取得し、ランダムでユニークな乱整数も含めてメール本文を生成する。
 - フォームに設置したスクリプトが、購読者を TO にして、生成した本文でメールを送信する。
 
1-2-3. メルマガ送信
次の動作をすべて Google ドキュメントに設置したスクリプトが行います。
- スプレッドシートを取得し、メルマガ送信チェックのセルが未入力な登録データを抽出する。
 - さらに、ジャンルのセル内容が一致する登録データを抽出する。
 - 抽出されたデータのメールアドレスを、メールの TO に設定する。
 - ドキュメントのファイル名を取得し、メール件名に設定する。
 - ドキュメントの変数 {name} を、抽出されたデータの名前で置換し、メール本文とする。
 - 抽出された購読者に対してメールを送信する。
 - メールを送信した日時を、スプレッドシートのメルマガ送信チェックのセルに反映する。
 
1-2-4. メール送信可能数を表示
- その日のメール送信可能残数を取得する。
 - メッセージボックスを表示し、取得した残数を表示する。
 
2. メルマガ登録・配信システムを作っていきます♪
2-1. フォルダ・ファイル構成
Google ドライブに次の構成でフォルダ、ファイルを作成いたしました。
- 「メルマガサンプル」フォルダを作成し、そこにすべてのファイルを置いた。
 - メルマガ登録フォーム ← Google フォーム
 - メルマガ登録フォーム ← 同名フォームから作成した Google Apps Script
 - メルマガ登録フォーム(回答) ← Google スプレッドシート
 - メルマガ登録フォーム(回答) ← 同名スプレッドシートから作成した Google Apps Script
 - メルマガ海テンプレート ← Google ドキュメント
 - メルマガ山テンプレート ← Google ドキュメント
 
2-2. メルマガシステムづくり
実際に次の流れで作成していきました。
2-3. フォルダの作成
- Google ドライブで、新規 > フォルダから「メルマガサンプル」フォルダを作成
 - 「メルマガサンプル」フォルダへ移動
 
以下、全てこのフォルダ内での作業となります。
2-4. メルマガ登録フォームと回答スプレッドシートの作成
2-4-1. フォームの作成
Google フォームを「メルマガ登録フォーム」という名前で新規作成し、次の項目を作成しましたの。
なお、「確認ページ」ブロックの「別の回答を送信するためのリンクを表示」にチェックが入っておりましたが、外しました。
| 質問のタイトル | 質問の形式 | 詳細設定など | 必須の質問 | 
|---|---|---|---|
| 氏名 | テキスト | 特になし | チェックした | 
| メールアドレス | テキスト | データの検証にチェックし、「テキスト」「メールアドレス」を選択 | チェックした | 
| ジャンル | 複数選択 | 「海」「山」 | チェックした | 

2-4-2. 回答スプレッドシートの作成と ID の取得
- フォームの「回答を表示」をクリック
 - 回答が記録されるスプレッドシートが自動的に作成され、表示された。以下、スプレッドシートでの作業
 - ツール > スクリプトエディタからスクリプトプロジェクトを開き、「メルマガ登録フォーム(回答)」と名前をつけた。
 - 次のコードに置き換えて実行し、このスプレッドシートの ID をコピーしておく。
function checkId() { Logger.log(SpreadsheetApp.getActiveSpreadsheet().getId()); } 
フォームとその回答を記録するスプレッドシートはこの時点でできあがっております。ですのでフォームから実際に回答をしてみてスプレッドシートに反映されることを確認いたしました。
2-4-3. フォームのコードを書く
ここで先ほど取得したスプレッドシートの ID を使用いたします。
また、乱整数の生成方法は以前のこの投稿を使用しております。
フォームのメニューで、ツール > スクリプトエディタからスクリプトプロジェクトを起動し、名前を「メルマガ登録フォーム」としました。
プログラムが行う処理の流れです。なお、このプログラムが動く前に、すでに入力した回答がスプレッドシートに反映されております。
- スプレッドシートへアクセスし、今まで挿入された登録者 ID を取得し、ランダムでユニークな乱整数(新たな登録者 ID)を生成する。
 - フォーム回答を取得し、新たな登録者 ID も含めてメール本文を生成する。
 - 購読者を TO に、発行者を BCC にして、生成した本文でメールを送信する。
 
メルマガ登録フォーム > コード.gs
/**
 * フォーム回答に ID を付与し、回答をメール送信します。
 *
 * @param {object} e フォーム回答内容
 */
function sendform(e) {
  // フォーム回答を取得
  var name = e.response.getItemResponses()[0].getResponse();
  var mail = e.response.getItemResponses()[1].getResponse();
  // 回答入力済みのスプレッドシート行を取得
  var spreadsheet = SpreadsheetApp.openById(★ Google スプレッドシート「メルマガ登録フォーム(回答)」の ID ★);
  var sheet = spreadsheet.getSheets()[0];
  var row = sheet.getLastRow();
  // 登録 ID 挿入列を取得し1次元配列生成
  var values = sheet.getRange(2, 5, row).getValues();
  var array = Array.prototype.concat.apply([], values);
  // ユニークな登録 ID を生成
  var id = getOtherRandomInt_(array, 1, 9999);
  // 回答行のカラムに登録 ID を設定
  sheet.getRange(row, 5).setValue(id);
  // メール送信
  var body = name + "様\n";
      + "メールマガジンにご登録いただきありがとうございました。\n\n"
      + "登録ID: " + id + "\n\n";
  MailApp.sendEmail(mail, "登録受け付けました", body);
}
/**
 * numberArray 配列に無く、かつ、min から max までの範囲の
 * 乱整数を返却します。
 * 返却できる乱整数がない場合は undefined を返却します。
 *
 * @param {array} numberArray 除外対象となる数値の一次元配列
 * @param {number} min 乱数の最小値
 * @param {number} max 乱数の最大値
 * @return {number|undefined} min から max までの乱整数。返却できる乱整数がない場合は undefined
 */
function getOtherRandomInt_(numberArray, min, max) {
  // min から max までの配列を生成
  var minMaxArray = [];
  var count = max - min + 1;
  for (var i = 0; i < count; i++) {
    minMaxArray[i] = min;
    min++;
  }
  // 返却対象となる数値を抽出
  // minMaxArray から numberArray と一致する要素を除去
  var candidateArray = minMaxArray.filter(function(element, index, array) {
    // this は filter の第 2 引数の numberArray
    return (this.indexOf(element) === -1);
  }, numberArray);
  // 返却乱数の配列要素番号を生成
  // candidateArray (返却乱数候補の数値が入った配列) の要素数を範囲として乱数を生成
  var candidateArrayLengthRandom = getRandomInt_(0, candidateArray.length - 1);
  // 返却乱数を配列から取り出して返却
  var randomInt = candidateArray[candidateArrayLengthRandom];
  return randomInt;
}
/**
 * 指定した数値内の乱整数を返却します。
 *
 * @param {number} min 乱数の最小値
 * @param {number} max 乱数の最大値
 * @return {number} min から max までの乱整数
 */
function getRandomInt_(min, max) {
  return Math.floor( Math.random() * (max - min + 1) ) + min;
}
2-4-4. トリガーの設定
メルマガ登録フォームのスクリプトで次の操作をいたします。
- リソース > 現在のプロジェクトのトリガー
 - 次を設定して、保存
- 実行: sendform
 - イベント: フォームから > フォーム送信時
 
 
このトリガー設定はよく忘れてしまいます><。コードを書いて、安心してしまいますの><。コードを書いても、どのタイミングで実行させるのか、指定してあげませんといつまで経っても動かないプログラムですの。
2-4-5. 動きの確認
これで完成です。フォームに登録すると自動的にメールが送信されること、スプレッドシートの左から5列目、つまり E 列に乱整数が追加されることを確認します。
なお、乱整数は既存の回答には影響を与えません。つまり、フォームから回答して追加された行にのみ乱整数が追加されます。
2-5. メルマガテンプレートとなるドキュメントの作成
2-5-1. ファイルと文章の作成
- Google ドキュメントを新規作成し、ファイル名を「メルマガ海テンプレート」とした。
 - メルマガのテンプレートを書く。フォームで登録した名前が置き換わる変数「{name}」を入れておく。
 
{name}さま、こんにちは!
メルマガ本文段落1。
メルマガ本文段落2。ダミーテキスト、ダミーテキスト、ダミーテキスト、ダミーテキスト、ダミーテキスト、ダミーテキスト、ダミーテキスト、ダミーテキスト、ダミーテキスト。メルマガ本文段落3。ダミーテキスト、ダミーテキスト、ダミーテキスト。
それでは{name}さま、ごきげんよう♪
署名部分

2-5-2. コードの作成
コードを書く前に、次の作業をいたしました。
- ツール > スクリプトエディタ
 - プロジェクト名を、メルマガ海、とした。
 
そして、次のコードでメルマガを送れるようにいたします。
メルマガ海 > コード.gs
/**
 * メニューにメルマガを追加します。
 */
function onOpen() {
  var ui = DocumentApp.getUi();
  var menu = ui.createMenu("メール");
  menu.addItem("メルマガを送信", "sendMagazine");
  menu.addToUi();
  menu.addItem("メール送信残数", "showRemainingDailyQuota");
  menu.addToUi();
}
/**
 * 本日のメール送信可能残数を表示します。
 */
function showRemainingDailyQuota() {
  var mailNumber = MailApp.getRemainingDailyQuota();
  var remainingDailyQuotaMessage = Utilities.formatString("本日のメール送信可能残数は %s です。", mailNumber);
  DocumentApp.getUi().alert(remainingDailyQuotaMessage);
}
/**
 * メルマガを送信します。
 */
function sendMagazine() {
  var genre = "海";
  // メルマガタイトル取得
  var title = DocumentApp.getActiveDocument().getName();
  // メルマガ本文取得
  var content = DocumentApp.getActiveDocument().getBody().getText();
  // メルマガ登録スプレッドシート
  var sheet = SpreadsheetApp.openById(★ Google スプレッドシート「メルマガ登録フォーム(回答)」の ID ★).getSheets()[0];
  var row = sheet.getLastRow();
  for (var i = 2; i <= row; i++){
    // メルマガ送信チェックに値が入っていたら送信しない
    if (sheet.getRange(i, 6).getValue() !== "") {
      Logger.log("メルマガ送信チェックに値が入っている: " + i);
      continue;
    }
    // ジャンルが一致しなければ送信しない
    if (sheet.getRange(i, 4).getValue() != genre) {
      Logger.log("ジャンルが一致しない: " + i);
     continue;
    }
    // ここまで到達した場合は、メルマガ送付
    // スプレッドシートから氏名を取得
    var name = sheet.getRange(i, 2).getValue();
    // メルマガ本文の氏名を置換
    var bodyText = content.replace(/{name}/g, name);
    // スプレッドシートからメールアドレスを取得
    var mail = sheet.getRange(i, 3).getValue();
    // メルマガ送信
    MailApp.sendEmail(mail, title, bodyText);
    // メルマガ送信チェック
    sheet.getRange(i, 6).setValue(new Date());
  }
}
2-5-3. 動作の確認
今回は、トリガーの追加は不要ですの。ドキュメントのメニューから起動するからですわね♪
できあがりましたら、ドキュメントをリロードします。すると「メール」メニューが追加されることと存じます。
おすすめの確認の進め方としては、まず「メール送信残数」を実行してみることですの。あまり複雑なコードではありませんので、ドキュメントとスクリプトが連携されているかどうかをまず確認することができます。
続きまして、「メルマガを送信」です。スプレッドシートのメールアドレス宛にメルマガが送信され、送信完了したら F 列(左から 6 列目)にタイムスタンプが追加されていることを確認致します。

これでひと通りのシステムが完成しました!
あとはこの「海」のテンプレートを使って、「山」のテンプレートも作成しておきましょう♪
2-6. もう一つのメルマガテンプレート「山」の作成
こちらは、簡単です。「海」テンプレートをコピーして一部変更するだけですの。
- 「メルマガ海テンプレート」をコピーして「メルマガ山テンプレート」にファイル名を変更する。
 - スクリプトエディタからコードを開き、「var genre = “海”;」を「var genre = “山”;」へ修正する。
 
3. コードやシステムの課題
次の点を改善、リファクタリングできると存じます。
- メルマガ登録時に CC に発行者を追加する。
 - Google スプレッドシートにアクセスする回数が多すぎる、と思う。きっと 1 回のアクセスで必要範囲をまとめて取得し、追加・編集・削除したデータを 1 回のアクセスで Google スプレッドシートに書き込むようにできるはず。
 - メルマガ送信時にメニューから項目を選んで即実行なため、ご送信しないようにしたい。確認のウインドウを表示して、OK なら送信、といったようにしたい。
 
4. おわりに
いつも思いますけれども、コードが書き上がってみますとわずかこれだけを書くのに毎度苦労しますの、、、と感じます。まだまだ未熟ですわ><。
ですけれども、毎度新しい発見もございます♪これが楽しいですの♪
以上です。
