【GAS】スプシをメール送信キューにする
メール送信の統一
小規模なら、わざわざサーバ側言語等でメール送信処理を作らずとも
サーバ→スプシへ値を連携し、GASからメール送信することで
GASをメールサーバ扱いできる。
発火タイミング
メール送信処理の発火タイミングが大切。
今回は、特定のセルが更新されたときに、トリガーをセットする処理でいく。
F1セルに値が入ると、
G1セルにはGASで作った関数を読んでいて、引数でF1を参照しているので再計算が走り
関数をキックできる。
/** キック処理 シート編集で起動 */
function ignite(flg) {
if (flg === 'ok') setTrig() // ※「setTrig関数」は自作のもの。あとで説明します
// 本当は ↓ こうしたいが
// SpreadsheetApp.getActiveSheet().getRange('F1').setValue('done')
// Exception: You do not have permission to call setValue(行 32)が発生しました。
// になる
return 'regist'
}
コメント中にも記載したが、肝はignite関数の再計算のタイミング。
スプシの再計算タイミングは
1️⃣シートロード時
2️⃣関数の引数で参照するセルが更新された時
であり、現状2️⃣でキックしているが、1️⃣でも動いてしまう。
とはいえ、ignite関数のようにスプシ上で読んでいる関数は、
図形に登録して実行したときのGAS実行を認証する画面が出ないので、permissionエラーでsetValueができない。現状これが最適解。
トリガー処理を準備
空回りで実行してしまうことの制御やロジック部の外だし、実行時間6分の制限がGASにあることを含め
トリガーを用いることを強く推奨します。
トリガーとはGAS上でのキューのようなもので、
1️⃣関数名と
2️⃣トリガー登録〜関数実行までの遅延時間 (時間ベースなら)
を登録できる。
以下では、トリガーを登録する処理 = setTrig、登録済みトリガーをクリアする処理 = delTrigを作った。
/**
* ★トリガーの呼ばれ方
* 「ignite」関数をスプシで呼ぶと
* 引数セルが更新されたタイミングで
* 「exe」関数が実行される。
*
* ★機能実装の方法
* 「exe」関数にて機能を実装する。
* exe関数冒頭でdelTrig関数を実行すること
*/
/** トリガー設定 */
const TRIGGER_FUNCTION = 'exe'
const TRIGGER_KEY = 'mail_trigger_key'
const RETRY_DELAY = 10 * 1000
/** トリガーセット */
const setTrig = () => ScriptApp.newTrigger(TRIGGER_FUNCTION).timeBased().after(RETRY_DELAY).create().getUniqueId()
/** トリガー削除 */
const delTrig = () => {
let triggers = ScriptApp.getProjectTriggers()
if (triggers.length === 0) {
Logger.log(`削除対象のトリガーidがない`)
return
}
triggers.forEach(v => {
Logger.log(`トリガーを削除 id: ${v.getUniqueId()}`)
ScriptApp.deleteTrigger(v)
})
}
よってセルが更新されてGAS関数がキックされたら、直でロジックでなく
トリガーを起動し、ちょっと待ってからロジックが走るようになりました。
メール送信の準備
Drive上のファイルを添付ファイルとしたいことも含め、もろもろアクセスの許可をする
{
"timeZone": "Asia/Tokyo",
"dependencies": {
},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"oauthScopes": [
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/script.scriptapp",
"https://www.googleapis.com/auth/gmail.send"
]
}
メール送信の処理
さて、exeという名前の関数がトリガーから呼ばれるようにしたので
ロジックはexeという関数の中に書いていきましょう。
なんかの場合に重複してキックされてトリガーがいっぱい登録されちゃってると悲しいので
先頭で、念の為トリガーをクリアしておきます。
function exe() {
delTrig() // トリガーをクリア
トリガー経由で読んでいるので、SpreadsheetApp.getActiveSpreadSheet()ではなく
ID指定で開きます
const SHEET_ID = 'XXXXX'
let sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName('XXXX')
let getVal = cell => sheet.getRange(cell).getValue()
// 「A1」とかを入れると直で値とれる関数をつくってみた
メール送信部は、キューとなっているスプシの表を全量読み
セルから値を取得
指定されている場合、添付ファイルを Driveから取得する(名前重複ファイルがあったら最初の一つ)
メール送信。ファイルが画像であれば、インライン要素としてhtml形式にする
キュートしていたスプシの表と、添付し終わったファイルを削除して終了
let lastRow = sheet.getLastRow()
// キューなし
if (lastRow === 1) {
Logger.log('no queue')
return
}
// 2行目から下のキューを送信
[...Array(lastRow - 1)].map((_, i) => i + 2).map(r => {
// 1. セルから値を取得
let to = getVal(`A${r}`)
let cc = getVal(`B${r}`)
let subject = getVal(`C${r}`)
let body = getVal(`D${r}`)
let attNm = getVal(`E${r}`)
// 2. 指定されている場合、添付ファイルを取得する
let attF = ""
let attFId = ""
if (attNm !== "") {
const files = DriveApp.getFolderById('ファイルがあるDriveフォルダのID').getFiles()
while (files.hasNext()) {
let file = files.next()
if (file.getName() === attNm) { // ファイル名で最初に一致したものを添付. ファイル名称は一意でないので、命名に注意
attF = DriveApp.getFileById(attFId = file.getId()).getBlob()
break
}
}
}
// 3. 送信
let options = {cc: cc, name: 'おしらせbot'}
if (attF !== "") { options.attachments = attF }
if (attF !== "" && attNm.endsWith("png") || attNm.endsWith("jpg")) {
body = body.replaceAll('\n', '<br>')
body += `<br><img src='cid:inlineImg' style="width: 100%;">`
options["htmlBody"] = body
options["inlineImages"] = { inlineImg: attF }
}
GmailApp.sendEmail(to, subject, body, options)
Logger.log(`send queue row#${r}`)
// 4. 添付ファイル、キューを削除
if (attFId !== "") {
DriveApp.getFileById(attFId).setTrashed(true)
}
sheet.getRange(`A${r}:E${r}`).clearContent()
})
}
レポ
secret系があるのでGASをそのまま公開はできませんが
一応githubに挙げているのでおいておきます。
ALHについて知る
↓ ↓ ↓ 採用サイトはこちら ↓ ↓ ↓
↓ ↓ ↓ コーポレートサイトはこちら ↓ ↓ ↓
↓ ↓ ↓ もっとALHについて知りたい? ↓ ↓ ↓