Google Apps ScriptでHTMLメルマガ配信システムを作る【GAS】
Google Apps Scriptを使って本格的な社内システムを作る登竜門となるのが、メールの配信。但し同一の内容ではなく、人に合わせて内容を差し替える必要があるケースが殆どです。
その場合、一個ずつ手動で作って送信はあまりにも非生産的。さらに素っ気ないテキストメールだと読み手に伝わらないので、HTMLメールで送りつけたい。こんなケースに使えるのが今回のHTMLメルマガ配信システム。但しあまりにも大量に送ると、MailAppの制限に引っかかってしまうので、そこを考慮して作る必要があります。
過去には同じような仕組みでレスポンシブなメールを送る仕組みを作ってます。
目次
今回使用するスプレッドシート
個人毎に1通づつ送るので、いわゆるbccにせずに送って情報漏えいであったり、間違った人の所へ別の人の内容を送るといった事が低減できるのが今回の仕組みです(但し、スプレッドシートに書き込む内容を間違えれば意味がないですが)
使い方
実行方法
スプレッドシートを開くと、メニューに「メルマガ」というものが表示されるので、スプレッドシートの所定の欄を入力してから、「メルマガ」-> 「一斉送信」を実行すると、送信確認が出ます。実行するとスプレッドシートの内容を差し込んだHTML形式のメールが送られる仕組みになっています。
※一斉送信2は、createTemplateFromFileにて生成時に差し込みデータもつけて渡す場合の事例になります。どちらも同じ結果が得られます。
図:送信アラートが出るようになってます
制限
Google Apps Scriptに於いて、メール送信は結構厳しい送信制限が課せられています。
- ドメイン内の場合は、2000通/1日まで。
- 外部宛の場合は、1500通/1日まで。
- メールの読み書きだけならば、50000回/1日まで。
- メール本文のサイズは400KB/1通まで。
- メールの添付ファイルは合計25MB/1通まで。
- メールの添付ファイルは250個/1通まで。
- メールの受信者は50宛先/1通まで。
以上のようになっています。また、表向き制限値が掛けられていませんが、一度に大量のメールを連続送信すると、429エラーなどが起きる可能性があります(UrlfechAppはそういった制限があるのは確認)。よって、1分間に3通程度になるように、1通送信毎にウェイトを入れておくと良いでしょう。
今回は1人ずつ送るので、メールの受信者制限に掛かることはありませんが、同一メールを送りつける場合には宛先を50個毎に区切って送る必要があるので、要注意です。
1 2 |
//次のUrlFechApp実行まで待ちまでしばらくスリープさせる(3秒間) Utilities.sleep(3000); |
メール本文の改造
メール本文は今回はHTMLメールであるため、GASのコード内には存在しません。index.html内に構築していく必要があります。HTMLメールですので、CSSが使えたり、リンクを張ったり自在に作れる為、通常のメールを送るよりも遥かに広い表現力を実現できるのが今回の仕組みの魅力ですね。
今回は差し込みメールであるため、詳細な仕組みはソースコードの項目のHTML側コードを参照してください。body以下にメール本文、Head部分にCSSを記述したりします。JavaScriptは記述しても意味がない(ブロックされます)ので、本当にメールのテンプレート内での利用に限られます(テンプレート整形処理でjavascriptを使うことはあります)
図:body以下にメール本文をHTML形式で記述する
ソースコード
GAS側コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
//グローバル変数 var mailarr; function mailman() { //送信アラートを出す var ui = SpreadsheetApp.getUi(); var re = ui.alert("送信アラート", "一斉送信しますか?", ui.ButtonSet.YES_NO); switch(re){ case ui.Button.YES: //処理を継続する break; case ui.Button.NO: ui.alert("送信をキャンセルしました"); return 0; break; } //スプレッドシートのデータを取得 var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("mailman"); var ss = sheet.getRange("A2:D").getValues(); //メール本文を差し込んで送信 for(var i = 0;i<ss.length;i++){ try{ //1行データを取り出す mailarr = ss[i]; //メアドが空なら処理を終了 if(mailarr[0] == ""){ break; } //差し込んで本文を取得 let html = HtmlService.createTemplateFromFile('index').evaluate().getContent(); //メールを送信 MailApp.sendEmail({ to: mailarr[0], subject: mailarr[1], htmlBody:html, }); }catch(e){ ui.alert("失敗しました。\n" + e.message) return; } } //終了メッセージ mailarr = []; ui.alert("送信完了"); } |
- 冒頭に1レコード分のデータを格納するためのグローバル変数mailarrを用意しておきます。HTML側から変数にアクセスするには、HTML側でスクリプトレットで変数を取ってくる関数をGASで用意しておくか?グローバル変数で入れておくか?また、createTemplateFromFileでHTML呼び出し時に同時に値を渡す必要があります。メール送信の関数内の変数を取り出しても、HTML側からは参照できません。
- forループで取得したスプレッドシートのデータの内、1レコード分をmailarrに格納
- createTemplateFromFileを使ってindex.htmlの内容を取得。この時、mailarrの内容が差し込まれて、結果のHTMLをgetContentで取得しています。
- 送信時はMailAppを使いますが、htmlbodyに対して取得したHTMLを割り当てます。
- 送信エラーなどの対処のために、エラートラップを用意してあります。
- 実際にはスプレッドシート側に送信ステータスの列を用意して、送信完了したら送信済みが入るようにし、エラー時には未送信のものを継続して次回から送信するような仕組みを用意しておくとベターです。
- また、返信されると困る場合には、noReplyオプション(Google Workspaceのみ利用可能)を付け加えたり、またreplyToオプションで返信先を自身ではなく別のメアド宛にしておくのも業務ではよく利用するテクニックです。
createTemplateFromFileで呼び出し時に同時に変数を渡す場合は以下のような感じで渡す事が可能です。HTML側は普通に<?=mailarr[0] ?>で渡された変数から取り出せます。
1 2 3 4 5 6 7 8 |
//HTML生成と同時に配列情報を渡す var output = HtmlService.createTemplateFromFile('index') //変数dataをつけてevaluateで渡す output.mailarr = mailarr; //ページを生成する var html = output.evaluate().getContent(); |
HTML側はこの変数を直接スクリプトレットで指定すれば取り出せます。
HTML側コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<!DOCTYPE html> <html> <head> <base target="_top"> </head> <body> <p>今日のお題:<?=mailarr[1] ?></p><br> <p><?=mailarr[3] ?> <?=mailarr[2] ?>様 こんにちは、メルマガ太郎です。</p> <div> 今日は素敵な情報をお届けいたします。 </div> </body> </html> |
- createTemplateFromFileにて呼び出されてるので、GAS側の変数をスクリプトレットを使って参照が可能です
- スクリプトレットを使って差し込みたい場所に変数を呼び出させて入れ込んでいます。
createTemplateFromFileやスクリプトレットについては、以前のエントリーで記述しているので、以下のリンク先を参照してみてください。
大量に送信したい場合には
本当のメルマガのように大量に送信したい場合には、Google Apps ScriptのMailAppで送るのは現実的ではないでしょう。あくまでも社内の少数のメンバーに対して通知で送る場合には非常に有効ですが、外部のメンバーに対して大量に送信したい場合には向いていません。
Microsoft365のGraph APIを利用した場合には、10000通/1日くらい送信可能であったり、送信制限も緩やかであり、またUrlfetchAppは10万回/1日利用可能であるため、メール利用ならばMicrosoft365を別途用意しておくのも良いです(とはいえ、UrlfetchAppは10秒間に3回くらいしかアクセスできず、またGASは6分の制限があるのでちょっとね・・・・)。以下のリンク先では、GASからGraph APIのOutlookサービスでメールを送る仕組みを作っています(実際に自分も社内でNode.js + Graph APIでメール通知システムは作ってます)
また、同一内容で送りたい場合は、まぐまぐなどが昔から利用されていますね。但し、REST APIが用意されてるわけじゃないので、月額有料のメール配信システムを利用するほうがREST APIも使えるケースがあるので、そちらを利用したほうが良いかと。
officeの杜さん、こんにちわ。
sergiといいます。参考になる記事ありがとうございます。
あの質問なのですが、今現在同じような方法でHTMLメールのメルマガをつくっておりまして、googleworkspaceを利用して他社さんに300通ぐらい送ろうと思ってるんですけど、
Utilities.sleep(3000);はfor文の最後に使えば、連続でメールを送るに時間を置くことが可能になるということでしょうか?
sergi様
officeの杜管理人です。
基本的にはforやwhile文の最後(1通送信後)にUtilities.sleepでウェイトが掛かるので、連続メールでGmailのリミットに引っかかることはないかと思います。
この連続送信リミットは記載がなく、どれくらいウェイトを入れたらよいのか?は、わからないのですが経験上、UrlfetchAppでは、10秒間に3回以上だと引っかかったので、およそ3000で指定すると、回避出来るのではないかと思います。
こちらのサイトでも同様の経験をされているようです。
久しぶりにこのサイトを検索したら返信があって感激しました。
返答できず申し訳ございません。メルマガの送信をやってみたところ基本的に連続送信でウェイトをかけずとも問題が起こることはありませんでした。一通送るのに時間がかかってしまうのが原因。ただ、六分以上継続して処理が行えない、HTMLメールの場合は100件/一日しか送れないなど個人のアカウントでは、限界があるなと感じました。六分以上処理が行えない問題はgoogle work spaceでも同じのようですが、100件/一日に関しては改善されるようです。
こんにちは。いつも大変参考になる記事をありがとうございます。
こちらのスクリプトをベースに、スプレッドシートから会社の代表メールを送信する社内システムを作っているのですが、ユーザー様からお問い合わせいただいたメールに対し、スプレッドシートでメールを返すと常に新規スレッドのメールになってしまい、お問い合わせと返答を一つのスレッドで管理することができませんでした。
スプレッドシートから、スレッドID(またはメッセージID)を指定して送信することはできるでしょうか?
たろーさん
officeの杜管理人です。
通常のMailAppではメールを送信するという機能に特化したメソッドなので、新規スレッドのメッセージしか送ることは出来ません。
しかし、もう一つあるGmailAppの場合、Gmailのスレッドを取得する際にgetIdでスレッドIDを取得することができます。
すでにスプレッドシートにお客様のメールの内容を記録できる仕組みがあるのであれば、GmailAppでスレッドを取得しgetIdでIDを取得して記録しておく。
また、その内容を元にGmailAppでメールを送信することで、スレッドにぶら下げて返信が可能です。
https://developers.google.com/apps-script/reference/gmail/gmail-thread?hl=ja#getId()
https://developers.google.com/apps-script/reference/gmail/gmail-app?hl=ja#sendemailrecipient,-subject,-body
早速のお返事ありがとうございます!
また、GASでぶら下げ返信ができる/できないがまず不明だったため、ざっくりした質問だったこと申し訳ありません。
別シートに受信メールの管理表があり、MessageID、ThreadIDはすでに抜き出して記録してある状態です(重複管理の都合で)
管理表からメール作成シートの特定セルに返信先IDをコピペして、送信ボタンを押したときセルの値を拾ってReplyできないだろうかと考えています。
ただ、セルに入力されたIDはString状態なせいか、例えばgetMessagesForThreadなどに代入してもエラーを返してくるんですよね……。
新たにgetIdするしかないでしょうか?
現在はGmailApp.sendEmailで送信していますが、前述のとおりすべて新規Threadのメールになっています。
リファレンスも読ませていただきましたが、付け焼き刃でGASを使っている身では、結局sendEmailのどのオプションにID情報をもたせればReplyになるのか理解しきれずでした……。
既に既存のメールメッセージに関して、messageId等が取得し記録出来ているならば、それらに対しての返信は、sendMailではなく
以下のようにIDを元にメッセージを取得したものに「reply」や「replyAll」などでリプするのが良いです。
以下のコードは受信トレイの未読10件を取得し、
スレッドを1つずつ取得して速攻で返信する
messageIdを取得し、そのIDを元にgetMessageByIdでメッセージを取得したものに、返信
の2パターンを記述しています。今回の内容ならば、後者のmessageIdを元に取得してreplyで返すのが適切でしょう。replyAllは全員に返信になります。
https://developers.google.com/apps-script/reference/gmail/gmail-app?hl=ja#sendemailrecipient,-subject,-body
//未読メールのスレッドを書き出す
function unreadthread() {
const threads = GmailApp.search('is:unread', 0, 10);
threads.forEach(thread => {
//messageにそのままreply
thread.reply("速攻でリプするよ");
//スレッドの塊を取得
const messages = thread.getMessages();
//スレッドの塊に対して1つずつ返信
messages.forEach(message => {
//メッセージのIDを取得
var msgid = message.getId();
//messageIdを元にreply
var mailMessage = GmailApp.getMessageById(msgid);
mailMessage.reply("ID指定でメールを返すよ")
});
});
}