GASで宛先・本文が違うメールを一斉送信

こんばんは、@0yanです。
今日は経営企画のお手伝いで、宛先一人ひとりに本文が微妙に違うメールをGmailで一斉送信するためのコードを書きました。
JavaScriptもGASも初心者かつ滅多に触らないので多少苦労しましたが、良い機会だったので、それぞれの言語の備忘録も兼ねて記事にまとめることにしました。
同じことをやりたい人のお役に立てば幸いです。


# やりたかったこと
スプレッドシートに

+ 名前
+ 会社名
+ E-mail
+ 送信URL

を記載したシートを用意します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373977/03d635c1-5a8a-d214-570b-110510d19f01.png)

そのうえでスクリプトを実行すると、下図赤枠部分が宛先毎に異なるメールを一斉送信できる･･･これがやりたかったことです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373977/ef21c361-0f41-8933-1268-8d7b58a17402.png)


# コード全体像
先にコード全体を提示してしまいます。

```javascript
function sendQuestionnaire() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('シート1');
  const dataRange = sheet.getDataRange();
  
  // 名前、会社名、E-mail、送信用URLの列数を取得（列数固定だと、列数が変わったときに機能しなくなるため）
  let nameIndex = 0;
  let companyNameIndex = 0;
  let emailIndex = 0;
  let sendUrlIndex = 0;
  for (var i = 1; i <= dataRange.getNumColumns(); i++) {
    switch (sheet.getRange(row=1, column=i).getValue()) {
      case '名前':
        nameIndex = i -1;  // 二次元配列のループは0から始まるため、列数を -1 する（以下同様）
      case '会社名':
        companyNameIndex = i -1; 
      case 'E-mail':
        emailIndex = i -1;
      case '送信URL':
        sendUrlIndex = i -1;
      default:
        ;
    }
  }
    
  // E-mail送信
  const subject = 'アンケートご協力のお願い';
  const data = dataRange.getValues();
  for (var i = 1; i < dataRange.getNumRows(); i++) {
    let name = data[i][nameIndex];  // 0行目はヘッダー情報のためスキップ（以下同様）
    let company = data[i][companyNameIndex];
    let email = data[i][emailIndex];
    let sendUrl = data[i][sendUrlIndex];
    
    // メール本文
    let body = `
    ${company}
    ${name}様
    
    お世話になっております。
    
    この度、${name}様の率直なお気持ち、ご意見を頂戴したく、
    アンケートにご協力頂きたく存じます。
    
    【アンケートURL】
    ${sendUrl}
    
    何卒宜しくお願い申し上げます。
    `
    
    try {
      GmailApp.sendEmail(email, subject, body, options={from: 'abc@gmail.com', bcc: 'xyz@gmail.com'});
      console.log('送信OK: ${name} ${company} ${email} ${sendUrl}');
    } catch (e) {
      console.log('送信NG: ${name} ${e.name} ${e.message}');
    }
  }
}
```

##コード詳細
###序盤
+ JavaScriptの関数定義は`function 関数名(引数) { 処理 }`
+ JavaScriptでは、データ型の定義しない
+ 変数も関数も小文字から始まり、[ローワーキャメルケース](https://wa3.i-3-i.info/word13954.html)で記述
+ 変数宣言は`var`, `let`, `const`のいずれかから始まる
    + `var`･･･再宣言OK、再代入OK
    + `let`･･･再宣言NG、再代入OK
    + `const`･･･再宣言NG、再代入NG
+ GoogleスプレッドシートをGASで操作する際のざっくりとした段取りは以下のとおり
    1. Spreadsheetインスタンス生成
    2. SpreadsheetインスタンスからSheetインスタンス生成
    3. SheetインスタンスからRangeインスタンス生成
    4. RangeインスタンスからgetValuesメソッドで二次元配列生成

```javascript
function sendQuestionnaire() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('シート1');
  const dataRange = sheet.getDataRange();
```

###中盤
+ JavaScriptのfor文は`for (var i = 1; i <= n; i++) { Trueの処理 };`
+ Rangeインスタンスから行数/列数を取得するには`getNumRows`メソッドと `getNumColumns`メソッド
+ JavaScriptにはswitch文がある（構文は以下のとおり）
+ JavaScriptでフロー制御文中、何もしないときは`;`だけ書く
+ GASは行も列もインデックスが1から始まるが、二次元配列は0からなので厄介

```javascript
  // 名前、会社名、E-mail、送信用URLの列数を取得（列数固定だと、列数が変わったときに機能しなくなるため）
  let nameIndex = 0;
  let companyNameIndex = 0;
  let emailIndex = 0;
  let sendUrlIndex = 0;
  for (var i = 1; i <= dataRange.getNumColumns(); i++) {
    switch (sheet.getRange(row=1, column=i).getValue()) {
      case '名前':
        nameIndex = i -1;  // 二次元配列のループは0から始まるため、列数を -1 する（以下同様）
      case '会社名':
        companyNameIndex = i -1; 
      case 'E-mail':
        emailIndex = i -1;
      case '送信URL':
        sendUrlIndex = i -1;
      default:
        ;
    }
  }
```

###終盤
+ 前述のとおり、GASでRangeインスタンスから値を二次元配列で取り出したいときは`Range.getValues`メソッド
+ JavaScriptの[テンプレート文字列](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/template_strings)は便利。Pythonのf文字列と似てる
+ GASでメール送信は`GmailApp.sendEmail`メソッド
    + 第一引数：宛先メールアドレス
    + 第二引数：件名
    + 第三引数：本文
    + 第四引数：オプション（下記以外にも添付ファイル等、色々ある模様）
        + fromオプション：エイリアスが複数ある場合に送信元を指定
        + Bccオプション：Bccを指定
+ `Console.log`メソッド書いておくと、Apps Scriptの画面でログ見れて非常に便利（デバッグ中の表示が遅いのはめっちゃイライラするけど）

```javascript
  // E-mail送信
  const subject = 'アンケートご協力のお願い';
  const data = dataRange.getValues();
  for (var i = 1; i < dataRange.getNumRows(); i++) {
    let name = data[i][nameIndex];  // 0行目はヘッダー情報のためスキップ（以下同様）
    let company = data[i][companyNameIndex];
    let email = data[i][emailIndex];
    let sendUrl = data[i][sendUrlIndex];
    
    // メール本文
    let body = `
    ${company}
    ${name}様
    
    お世話になっております。
    
    この度、${name}様の率直なお気持ち、ご意見を頂戴したく、
    アンケートにご協力頂きたく存じます。
    
    【アンケートURL】
    ${sendUrl}
    
    何卒宜しくお願い申し上げます。
    `
    
    try {
      GmailApp.sendEmail(email, subject, body, options={from: 'abc@gmail.com', bcc: 'xyz@gmail.com'});
      console.log('送信OK: ${name} ${company} ${email} ${sendUrl}');
    } catch (e) {
      console.log('送信NG: ${name} ${e.name} ${e.message}');
    }
  }
}
```


#最後に
コートは[こちら](https://github.com/0yan/mailBroadcast)にもアップしました。
JavaScript & GAS初心者なので細かく書きました。
これを機に両言語とも避けないで使っていこうかな･･･と思います。