2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

42TokyoAdvent Calendar 2024

Day 9

GASでGmailAPIでエラハンする

Posted at

この記事は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を使用する方法もあるようですが、ここからだとアクセストークン等の認証関連を気にせずに済むメリットがあります。
Screenshot from 2024-12-09 15-14-46.png

解説

このコードの要点は4ステップです。

  1. 送信するメールの作成
  2. メールの送信
  3. 送信したメールが属するスレッドの取得
  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()を初めて使う機会になったので、これはこれで良かったのかなと思いました。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?