Google Apps Script(GAS)でのメール送信方法・Google Formでのメール送信インターフェース(J3-03)

# 1. Google Apps Scriptでのメール送信
* GASの関数として，`MailApp.sendEmail()`等が用意されている。これはnewでオブジェクト（インスタンス）を作成する必要がない，スタティック（いつでも実行可能）な`クラス.メソッド`である。 
* 詳しくは，[GASマニュアル](https://developers.google.com/apps-script/reference/mail/mail-app)を参照。
* 引数を渡してメールを送る次のメソッドでも良いが，一般には`sendEmail(message)`を使用するのが楽。
   
    sendEmail(recipient, subject, body)
    sendEmail(recipient, subject, body, options)
    sendEmail(to, replyTo, subject, body)

* `sendEmail(message)`は，一番フレキシブルで使いやすく，たくさんのオプションが利用できる。messageは{}でくくることで，[Advanced parameters](https://developers.google.com/apps-script/reference/mail/mail-app#sendEmail(Object))が利用できる。
* Advanced parametersの使用例は，次のサンプルプログラムを参照。to, cc, bcc, name subject, body等の便利なパラメータが利用できる。
* 次のサンプルプログラムでは，添付ファイルを送信する準備のため，`new Array()`で添付ファイルを格納する配列を用意しているが，使っていない。テストしたい場合は各自でマニュアルやネット上のサンプルを見て学習せよ。

```java:メール送信関数
logID= "**********";//手動でDocumentを新規作成しIDを調べておく
//ssID = "++++++++++";//手動でSpreadsheetを新規作成しIDを調べておく

function main(){
    var toAdr = "";//送り先アドレス
    var ccAdr = "";//Ccアドレス
    var bccAdr = "";//bccアドレス
    var subject = "";//メールの題目
    var name = "";//送り主の名前
    var files = new Array();//添付ファイル，どんな型でも，いくつでも格納できる配列，
                            //場所は確保してないのでpushメソッドで格納する
    var body =
      "メール送信テスト\n" +
      "送信日時：" + getDateAndTimeOfNow() + "\n" +
      "メール本文" + "\n";
    MailApp.sendEmail({to:toAdr, cc:ccAdr, bcc:bccAdr, subject:subject, name:name, body:body, attachments:files});
}

function getDateAndTimeOfNow(){// 現在日時を文字列でゲット
  var now = new Date();
  var year = now.getFullYear();//getYear()+1900
  var month = now.getMonth() + 1;
  var day = now.getDate();
  var hour = now.getHours();
  var min = now.getMinutes();
  var sec = now.getSeconds();
  return (""+year).slice(2,4) + ("0"+month).slice(-2) + ("0"+day).slice(-2) +'_'+ 
    ("0"+hour).slice(-2) + ("0"+min).slice(-2) + ("0"+sec).slice(-2);
}

//////////Docクラスの定義開始（コンストラクタとメンバ関数で構成）
//Docクラスのコンストラクタの記述
Doc = function(id){
  this.ID = id;
  this.doc = DocumentApp.openById(this.ID); 
  this.body = this.doc.getBody();
  this.docText = this.body.editAsText();
}
//Docクラスのメンバ関数の定義開始
//メソッドprintの定義，テキスト追加
Doc.prototype.print = function(str){
  this.docText.appendText(str);
}
//メソッドreplaceの定義，文字列置き換え
Doc.prototype.replace = function(src,dst){
  this.body.replaceText(src,dst);
}
//メソッドclearの定義，全消去
Doc.prototype.clear = function(){
  this.body.clear();
}
//メソッドgetIDの定義，ファイルIDを返す
Doc.prototype.getID = function(){
  return this.ID;
}
//指定秒数のウェイト，表示動作を遅らせたい時などに使用
Doc.prototype.waitSec = function(sec){
  var start = new Date().getSeconds();
  while((new Date().getSeconds()-start) < sec);
}
//指定ミリ秒のウェイト，表示動作を遅らせたい時などに使用
Doc.prototype.waitMiliSec = function(msec){
  var start = new Date(); //new Date()は，「1970年1月1日午前0時」からの通算ミリ秒を返す
  while((new Date()-start) < msec);
}
//今現在の日時を表示
Doc.prototype.printTodayNow = function(){
  var now = new Date();
  var year = now.getYear();
  var month = now.getMonth() + 1;
  var day = now.getDate();
  var hour = now.getHours();
  var min = now.getMinutes();
  var sec = now.getSeconds();
  this.docText.appendText(year +'_'+ ("0"+month).slice(-2) +'_'+ ("0"+day).slice(-2) +' '+ 
    ("0"+hour).slice(-2) +'-'+ ("0"+min).slice(-2) +'-'+ ("0"+sec).slice(-2));
}
//////////Docクラスの定義終了
```

__＜課題5＞__
__学校の自分の公式メールアドレス宛にメール送信し，メールの送信ができることを確認せよ。最低限，toAdr, subject, name, bodyは設定してから実行する。ファイルの添付をしない場合は，添付ファイル用の配列に何も代入する必要はない。送信すると次のように届くはずである。なおカンマで区切れば複数のアドレスに対してtoやCcも可能である。届いたメールをコピーペーストして，kadai5.txtとして指定フォルダに提出せよ。（なおループを組めばスパムメールも可能であるがやってはいけない。スパム防止のため，GASによるメール送信は，1日70通程度しか送信できないように上限が設定されている。）__

* メール送信テスト例
![メール送信テスト.PNG](https://qiita-image-store.s3.amazonaws.com/0/146927/8ad73776-237c-243a-8b41-756f4244f20f.png)

* 次のようにマウスでドラッグして，kadai5.txtにコピーペーストして，課題提出する。ドラッグコピーできない部分は提出しなくて良い。
![メール課題提出.PNG](https://qiita-image-store.s3.amazonaws.com/0/146927/8a1fa630-a61b-2ed5-681f-c1f56f3b0226.png)



# 2. Google Formでメール送信インターフェースを作成

* メール送信できたので，次はGoogle Formを利用して，時間指定メール送信用ユーザーインターフェース（UI）を作成する。
* UIに入力されたデータは，入力者にメールですぐに返し，内容を確認すると共に，修正可能にする。
* 修正は，返されたメール中にあるURLをクリックすることで，実現する。
*  今回は添付ファイルについては実装しない。htmlメールや画像の挿入等も行わない単純なテキストメールとする。
*  時間指定メール送信に必須な項目と必須でない項目がある。入力しなくても済む項目は入力必須とせず，絶対に必要となるものは，入力必須とする。
* ゴールは，送信日時指定メール送信システムなので，送信日時も入力できるようにする。
* 

__＜課題6＞__
__次の手順に従い，時間指定メール送信UIをGoogle Formを使って作成せよ。まずGoogle Driveに本日の日付のフィルダを作成し，その中に，Google Form「時間指定メール送信システム（山田太郎）」を新規作成してから始める。山田太郎，は自分の氏名に置き換えること。URLを添付ファイルとして，Teamsの課題提出を行え。__


1. to, date, timeは必須，cc, bcc, subject, bodyは必須でない。下記、サンプル画像は間違っているので正しく修正すること。
1. メールアドレスはメールアドレスのフォーマットしか受け付けないように設定せよ。
1. ~~replyTo,~~ nameについては，GAS中で決めうちする。
1. 参考手順を以下に示す。


---
![フォームの新規作成.png](https://qiita-image-store.s3.amazonaws.com/0/146927/528d3454-2128-b1a4-0cc9-69a9c3704b14.png)

---
![フォームの名前を変更.png](https://qiita-image-store.s3.amazonaws.com/0/146927/c7b45dd1-71fe-968a-d9bf-bb0a71648572.png)


---
![画像9.png](https://qiita-image-store.s3.amazonaws.com/0/146927/b30f5ce7-88bf-0116-68df-1b37068144c2.png)

---
![画像10.png](https://qiita-image-store.s3.amazonaws.com/0/146927/50cb688c-3d38-e486-1a3d-4003a3fa8bb5.png)

---
* プレビューをすると，完成形を見ることができる。

![画像11.png](https://qiita-image-store.s3.amazonaws.com/0/146927/c4e60ce1-d8bf-d5c4-16b1-2dafd46d11bb.png)

---
* URLを取得する。

![画像12.png](https://qiita-image-store.s3.amazonaws.com/0/146927/a649c319-eae8-4e0a-1b55-0ae8327633cc.png)

---
![画像13.png](https://qiita-image-store.s3.amazonaws.com/0/146927/84ec268b-edad-420f-3e01-e6eff1dae80c.png)

---
![画像14.png](https://qiita-image-store.s3.amazonaws.com/0/146927/c3af6778-2d2b-cca7-abc9-9235254c1a64.png)

---
* コピーしたURLは，テキストエディタにペーストし，「URL.txt」というファイル名で指定フォルダにアップロードする。

---
![画像17.png](https://qiita-image-store.s3.amazonaws.com/0/146927/08f7b49d-d09e-8b1a-7504-cb59e37f387c.png)


# 3.  Google Formに紐付けされたGASプログラムを作成

* 作成したGoogle Formで送信ボタンが押された時，実行されるGASプログラムを作成する。
* 送信ボタン押下時に実行するプログラムでは，Google Formに入力された内容を自分に送信し内容を確認できるようにするとともに，内容について修正もできるようにする。
* 指定日時になったら送信するプログラムは，次回以降作成する。
* フォルダ内にGoogle Document「log」を作成し，ファイルID確認のため，開いておく。
* Google Formにスクリプトを書くため，作成した「時間指定メール送信システム」フォームを開く。

![画像23.png](https://qiita-image-store.s3.amazonaws.com/0/146927/d137775c-e680-60cd-7e4b-e6f2833e1969.png)

---
* スクリプトエディタを開く。

![画像24.png](https://qiita-image-store.s3.amazonaws.com/0/146927/8ec658c4-0fe7-8458-dc11-9eb5547029a5.png)

---
* 「無題のプロジェクト」という名前になっているので，フォームと同じ「時間指定メール送信システム（山田太郎）」に変更する。（任意の名前でもかまわないが同じ方がわかりやすい）

![画像25.png](https://qiita-image-store.s3.amazonaws.com/0/146927/d614ae65-e145-049a-6881-c9973dc865f7.png)

---
* 最初から書かれているGASプログラム，「コード.gs」の4行は全削除する。
* 次の内容をコピーペーストする。「**********」の部分は，logのIDに置き換える。


```java:課題テンプレート
logID= "**********";//手動でDocumentを新規作成しIDを調べておく
//ssID = "++++++++++";//手動でSpreadsheetを新規作成しIDを調べておく

function submitForm(e){//フォームが送信されたら呼び出される，重複処理を避ける
  log = new Doc(logID);
  log.print('\n'+getDateAndTimeOfNow()+" スクリプト開始\n");
  var lock = LockService.getScriptLock();//ロックサービスのオブジェクトを生成
  try{
    lock.waitLock(30000);//複数のフォーム送信がほぼ同時にあった時，遅い方に最大30秒待ってもらう
    log.print("他のスクリプト実行要求をロック完了，最大30秒\n");
    main(e,log);
  }catch(err){
    log.print("発生したエラー："+err+'\n');
  }finally{
    lock.releaseLock();//次の送信のためにロック解除
    log.print(getDateAndTimeOfNow()+" リリースロック，次のスクリプト要求を受け付け開始\n");
  } 
}


function main(e, log){
  log.print("main関数開始\n");
  var itemResponses = e.response.getItemResponses(); 
  var message = '';

  log.print("入力内容取得開始\n");  
  for (var i = 0; i < itemResponses.length; i++) { 
    var itemResponse = itemResponses[i]; 
    var question = itemResponse.getItem().getTitle(); 
    var answer = itemResponse.getResponse();
    if(answer=="") answer="未入力";
    message += question + "\n　" + answer + '\n';
  }
  log.print("GoogleForm入力内容取得終了\n");  

  var editURL = e.response.getEditResponseUrl(); //回答編集用URLを取得
  log.print("編集用URLを取得\n");  
  
  log.print("入力内容確認メール送信開始\n");
  
  var toAdr = "";//送り先アドレス
  var ccAdr = "";//Ccアドレス
  var bccAdr = "";//bccアドレス
  var subject = "時間指定メール配信システム";//メールの題目
  var name = "";//送り主の名前
  var files = new Array();//添付ファイル，どんな型でも，いくつでも格納できる配列，
  //場所は確保してないのでpushメソッドで格納する
  var body =
      "メール送信テスト\n" +
        "送信日時：" + getDateAndTimeOfNow() + "\n" +
        "修正URL：" + editURL + "\n\n" +
        "メール本文：" + "\n" +
        message;
  MailApp.sendEmail({to:toAdr, cc:ccAdr, bcc:bccAdr, subject:subject, name:name, body:body, attachments:files});
  
  log.print("メール送信終了\n");
  log.print("main関数終了\n");  
  
}

function getDateAndTimeOfNow(){// 現在日時を文字列でゲット
  var now = new Date();
  var year = now.getYear();
  var month = now.getMonth() + 1;
  var day = now.getDate();
  var hour = now.getHours();
  var min = now.getMinutes();
  var sec = now.getSeconds();
  return (""+year).slice(2,4) + ("0"+month).slice(-2) + ("0"+day).slice(-2) +'_'+ 
    ("0"+hour).slice(-2) + ("0"+min).slice(-2) + ("0"+sec).slice(-2);
}

//////////Docクラスの定義開始（コンストラクタとメンバ関数で構成）
//Docクラスのコンストラクタの記述
Doc = function(id){
  this.ID = id;
  this.doc = DocumentApp.openById(this.ID); 
  this.body = this.doc.getBody();
  this.docText = this.body.editAsText();
}
//Docクラスのメンバ関数の定義開始
//メソッドprintの定義，テキスト追加
Doc.prototype.print = function(str){
  this.docText.appendText(str);
}
//メソッドreplaceの定義，文字列置き換え
Doc.prototype.replace = function(src,dst){
  this.body.replaceText(src,dst);
}
//メソッドclearの定義，全消去
Doc.prototype.clear = function(){
  this.body.clear();
}
//メソッドgetIDの定義，ファイルIDを返す
Doc.prototype.getID = function(){
  return this.ID;
}
//指定秒数のウェイト，表示動作を遅らせたい時などに使用
Doc.prototype.waitSec = function(sec){
  var start = new Date().getSeconds();
  while((new Date().getSeconds()-start) < sec);
}
//指定ミリ秒のウェイト，表示動作を遅らせたい時などに使用
Doc.prototype.waitMiliSec = function(msec){
  var start = new Date(); //new Date()は，「1970年1月1日午前0時」からの通算ミリ秒を返す
  while((new Date()-start) < msec);
}
//今現在の日時を表示
Doc.prototype.printTodayNow = function(){
  var now = new Date();
  var year = now.getYear();
  var month = now.getMonth() + 1;
  var day = now.getDate();
  var hour = now.getHours();
  var min = now.getMinutes();
  var sec = now.getSeconds();
  this.docText.appendText(year +'_'+ ("0"+month).slice(-2) +'_'+ ("0"+day).slice(-2) +' '+ 
    ("0"+hour).slice(-2) +'-'+ ("0"+min).slice(-2) +'-'+ ("0"+sec).slice(-2));
}
//////////Docクラスの定義終了

```

---
* 現在のプロジェクトのトリガーを設定する。編集メニューにトリガー設定がある。

---

![画像29.png](https://qiita-image-store.s3.amazonaws.com/0/146927/daf67cb7-a058-187e-177f-5e66b2f43a41.png)

---

![画像30.png](https://qiita-image-store.s3.amazonaws.com/0/146927/8c13d10f-bf90-cdf1-6df5-12a087b125e4.png)


---
* 承認の許可を求められるので，内容を確認して許可する。

![画像31.png](https://qiita-image-store.s3.amazonaws.com/0/146927/f7f9b613-834b-0896-3565-95b8e96cc4f6.png)

---

![画像33.png](https://qiita-image-store.s3.amazonaws.com/0/146927/916bb2ba-1fc2-a22e-6c36-e95d7bdf5567.png)

---
* logを見えるようにした状態で，フォームに入力して送信する。まだメールは送信できないが，間違っても人に迷惑をかけないため，送信先アドレスは自分のアドレスにすること。なお，今後，GASの実行前に「Ctrl+A Delete」でlogの内容を全部削除しておくと見やすい。

![画像27.png](https://qiita-image-store.s3.amazonaws.com/0/146927/9f64afb9-9e28-4493-870f-3abd9cc2972c.png)

---
* logに記録される内容を確認する。実行ログとエラーが表示されているはずである。

![画像34.5.png](https://qiita-image-store.s3.amazonaws.com/0/146927/5b5b770b-0067-1907-7a7f-599b391ad93f.png)

---
* エラーはメールアドレスが指定されてないためである。toAdrに，Googleにログインしている自分のメールアドレスを代入して，自分宛にメールを送るようにせよ。修正後，必ずフロッピーマークをクリックして，保存する。これを忘れると，フォームに入力しても反映されない。

* フォームについて戻るボタンで戻って，再度送信する。logの内容は今回は消去しないで比較する。

![画像34.png](https://qiita-image-store.s3.amazonaws.com/0/146927/209582fc-257b-2ba8-bdc1-aa57deedcee6.png)

---
* エラーがなくなっていることが確認できる。
![画像35.png](https://qiita-image-store.s3.amazonaws.com/0/146927/ccd02543-3bd3-29d7-0ada-63c9c1b26c15.png)

---
* 迷惑メールに振り分けられなれば，メールが届くので確認せよ。題名も内容もないため，プロバイダによっては迷惑メールとして削除されてしまう場合がある。

---
__＜課題7＞__
__フォームに入力した内容について，自分自身のGmailアカウントに入力内容確認のメールを送るようにプログラムを書け。届いたメールをpdf形式で保存・アップロードせよ。ファイル名は，「入力内容確認.pdf」とする。main関数の内容について，関数毎に何をしているか概要を書いたうえで，すべてのステートメント（「;」毎）についてシンプルに解説せよ。下のレポート例に従った形式で作成せよ。なお解説に必要なことはネット検索して学習する。「山田太郎 解説.txt」の様にファイル名を付けたうえで，アップロードせよ。１は完成しているので，２を追記すれば良い。__

参考：公式ページ
https://developers.google.com/apps-script/
（関数名等を検索すると説明やサンプルプログラムがある）

```:レポート例
１．function submitForm(e){}
・フォームが送信されたら実行される関数。
(1)　var log = DocumentApp.openById("1E.....Cro");
・ファイルIDでドキュメントをオープンして，logとして扱う。
(2) printDoc(log,'\n'+getDateAndTimeOfNow()+" スクリプト開始\n");
・logに，スクリプト開始時間と開始したことを記録。
(3) var lock = LockService.getScriptLock();
・ロックサービスのオブジェクトを生成。
(4) try{
    lock.waitLock(30000);
    printDoc(log,"他のスクリプト実行要求をロック完了，最大30秒\n");
    main(e,log);
  }catch(err){
    printDoc(log,"発生したエラー："+err+'\n');
  }finally{
    lock.releaseLock();
    printDoc(log,getDateAndTimeOfNow()+" リリースロック，次のスクリプト要求を受け付け開始\n");
  } 
}
・重複処理を防ぐため，スクリプト実行中にさらにフォーム送信があった場合に備えて，最大30秒ロックする。
・ロック完了した旨，ログに記録して，main関数を実行。
・エラーが発生していたら，そのエラーを表示。
・main関数が正常終了したら，ロックをリリースした旨，ログに記録。

２．function main(e, log){}
・入力内容を取得し，修正用URLとともにメール送信する。
(1)	log.print("main関数開始\n");
・main関数の開始をlogに記録。
・・・・・あとは各自で書くこと。
```