この記事は42Tokyo Advent Calendar 2024 、9日目の記事です。
はじめに
syamasawと申します。GoとGASとAPIが好きな42Tokyo生です。
42Tokyoに関わるナイスな記事のアイデアが思いつかなかったので、今回は普通にGASのことを話そうと思います。
経緯
GmailをGASで送信する際、GASの関数sendEmail
でメールを送信すると、そのメール送信が正常に行われたかが不明だった。そのため、レスポンスが帰って来るGmailAPIを用いることで、より良いエラーハンドリングが行えると考えた。
コード
function sendEmailByAPI(to, subject, body) {
try {
let obj = Gmail.newMessage();
let message = "From: me\r\n" +
`To: ${to}\r\n` +
`Subject: ${subject}\r\n\r\n` +
`${body}`;
obj.raw = Utilities.base64EncodeWebSafe(message, Utilities.Charset.UTF_8); // 日本語
let response = Gmail.Users.Messages.send(obj, "me");
Utilities.sleep(5000); // バウンス待ちで5秒待機
let thread = Gmail.Users.Threads.get("me", response.id);
for (let i = 0; i < thread.messages.length; i++) {
if (thread.messages[i].payload.headers.some(header => header.value.includes("mailer-daemon@googlemail.com"))) {
return {"OK": false, "message": "Send failure"};
}
}
return {"OK": true, "message": "Send success"};
} catch (error) {
Logger.log(error);
return {"OK": false, "message": error};
}
}
function test() {
let body = "GASのテストだよ〜"
let response = sendEmailByAPI("yourmail@example.com", "testMail", body);
Logger.log(response.message);
}
前提
今回は、GASのサービスから、GmailAPIを選択して使用します。GCP経由でAPIを使用する方法もあるようですが、ここからだとアクセストークン等の認証関連を気にせずに済むメリットがあります。
解説
このコードの要点は4ステップです。
- 送信するメールの作成
- メールの送信
- 送信したメールが属するスレッドの取得
- スレッドのチェック
順に見ていきましょう。
1. 送信するメールの作成
まず、Gmail.newMessage()
によってメッセージオブジェクトを作成します。最初の状態ではこのオブジェクトは空っぽのため、ここに情報を付加します。
次に、messageの文字列を作成します。ここでは、From, To, Subject, Body(本文)を記述します。GmailAPIでは、Fromの部分をmeにすることで、メールアドレスをハードコードせずに、関数を使った人のメールアドレスを使用することが出来ます。
そして、Utilities.base64EncodeWebSafe(data, charset)
を用いて、文字列をbase64にエンコードします。ここで注意したいのは、charsetを省略しないことです。charsetを省略した場合、日本語が文字化けします。
2. メールの送信
Gmail.Users.Messages.send()
を用いてメールを送信します。先程作成したメールのToで、メールアドレスのフォーマットに誤りがある場合(メールアドレスの@を忘れた、等)、この関数は例外をスローします。フォーマットが正しい場合は送信が行われます。
なお、送信先が不明(タイプミス等)の場合、ここではエラーになりません。
3. 送信したメールが属するスレッドの取得
Gmail.Users.Threads.get(userId, id)
を用いて送信したメールのスレッドを取得します。送信失敗した場合、少しのタイムラグの後スレッドにmailer-daemon@googlemail.com
からバウンスが届くので、これを検索するためにこの関数を使用します。
ここも同様に、userIdをmeにすることで、ハードコードを回避できます。
今回はバウンスのタイムラグを回避するためにsleepを用いましたが、場合によって届くのが遅れる可能性があるので、注意が必要です。(筆者の環境では、5秒で外したことはなかったです)
4. スレッドのチェック
各messageオブジェクトのpayloadプロパティ内のheaders配列から、someを用いてmailer-daemon@googlemail.com
を含むvalueプロパティを持つかをループで順に判定します。trueであった場合にはバウンスが存在するため、エラーを返します。
おわりに
9割9分の42Tokyo生には縁がない内容になって申し訳ないと思いました。
しかし、javascriptのArray.prototype.some()
を初めて使う機会になったので、これはこれで良かったのかなと思いました。