Webhook APIを使って添付ファイル付きメールを処理する(Google Cloud Functions + Java)

Customers Mail CloudのWebhookは2種類あります。

  1. メール受信時
  2. メール送信時

メール受信時のWebhookはその名の通り、メールを受け取った際に任意のURLをコールするものです。この記事では添付ファイル付きメールを受け取った際のWebhook処理について解説します。

フォーマットはマルチパートフォームデータ

Webhookの形式として、JSONとマルチパートフォームデータ(multipart/form-data)が選択できます。この二つの違いは、添付ファイルがあるかどうかです。JSONの場合、添付ファイルは送られてきません。今回のようにメールに添付ファイルがついてくる場合は、後者を選択してください。

Webhook設定ダイアログ

送信されてくるデータについて

メールを受信すると、以下のようなWebhookが送られてきます(データは一部マスキングしています)。JSONにしていますが、実際にはmultipart/form-dataです。

{
    "filter": "info@smtps.jp",
    "headers": [
      {name: 'Return-Path', value: '<user@example.com>'},
        :
      {name: 'Date', value: 'Thu, 27 Apr 2023 15:56:26 +0900'}
    ],
    "subject": "Webhookのテスト",
    "envelope-to": "user@smtps.jp",
    "server_composition": "sandbox",
    "html": "<div dir=\\\\\\\\\\\\\\\\"ltr\\\\\\\\\\\\\\\\">Webhookのテスト用メールです。<div>...</div></div>",
    "text": "Webhookのテスト用メールです。\\\\\\\\\\\\\\\\r\\\\\\\\\\\\\\\\n\\\\\\\\\\\\\\\\r\\\\\\\\\\\\\\\\n--\\\\\\\\\\\\\\\\r\\\\\\\\\\\\\\\\n...",
    "envelope-from": "info@smtps.jp",
    "attachments": 1,
    "attachment1": "...."
}

Google Cloud Functionsの準備

今回はローカルで開発する流れを紹介します。まず、適当なフォルダを作成します。今回はcmcとします。

mkdir cmc
cd cmc

ソース ディレクトリとソースファイルを格納するプロジェクト構造を作成します。

mkdir -p src/main/java/functions
touch src/main/java/functions/HelloWorld.java

HelloWorld.java を編集します。

package functions;

import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import java.io.BufferedWriter;
import java.io.IOException;

public class HelloWorld implements HttpFunction {
  @Override
  public void service(HttpRequest request, HttpResponse response)
      throws IOException {
    BufferedWriter writer = response.getWriter();
    writer.write("ok");
  }
}

次に build.gradle を作成します。

apply plugin: 'java'

repositories {
  jcenter()
  mavenCentral()
}
configurations {
    invoker
}

dependencies {
    // https://mvnrepository.com/artifact/com.google.code.gson/gson
    implementation group: 'com.google.code.gson', name: 'gson', version: '2.7'

  // Every function needs this dependency to get the Functions Framework API.
  compileOnly 'com.google.cloud.functions:functions-framework-api:1.1.0'

  // To run function locally using Functions Framework's local invoker
  invoker 'com.google.cloud.functions.invoker:java-function-invoker:1.3.1'

  // These dependencies are only used by the tests.
  testImplementation 'com.google.cloud.functions:functions-framework-api:1.1.0'
  testImplementation 'junit:junit:4.13.2'
  testImplementation 'com.google.truth:truth:1.4.0'
  testImplementation 'org.mockito:mockito-core:5.10.0'

}

// Register a "runFunction" task to run the function locally
tasks.register("runFunction", JavaExec) {
  main = 'com.google.cloud.functions.invoker.runner.Invoker'
  classpath(configurations.invoker)
  inputs.files(configurations.runtimeClasspath, sourceSets.main.output)
  args(
    '--target', project.findProperty('run.functionTarget') ?: '',
    '--port', project.findProperty('run.port') ?: 8080
  )
  doFirst {
    args('--classpath', files(configurations.runtimeClasspath, sourceSets.main.output).asPath)
  }
}

ライブラリをインストールします。

gradle build

そして、以下のようにコマンドを実行します。

gradle runFunction -Prun.functionTarget=functions.HelloWorld

これで、 http://localhost:8080 にてサーバーが立ち上がります。

Javaのコード

処理は HelloWorld.javaservice 関数内に記述します。

public void service(HttpRequest request, HttpResponse response)
        throws IOException {
    BufferedWriter writer = response.getWriter();
    // ここに記述

    writer.write("ok");
}

マルチパートフォームデータを取得する

multipart/form-data で送られてくるデータは request.getQueryParameters() でMap型にて受け取れます。たとえば、添付ファイルの数を取得する場合は以下のようにします。この数が1以上の場合、添付ファイルがあるということです。

Map<String,List<String>> map = request.getQueryParameters();
int attachments = Integer.parseInt(map.get("attachments").get(0));

その他のフィールドも同じように取得できます。

String filter = map.get("filter").get(0);
System.out.println(filter);
String subject = map.get("subject").get(0);
System.out.println(subject);

ファイルは request.getParts().values() にて取得します。以下はテンポラリフォルダの中に、添付ファイルを保存する例です。

// 必要なインポート(ファイルの上部に記述)
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Map;
import java.util.List;

// 以下はservice関数内
String tempDirectory = System.getProperty("java.io.tmpdir");
for (HttpRequest.HttpPart httpPart : request.getParts().values()) {
  String filename = httpPart.getFileName().orElse(null);
  if (filename == null) {
    continue;
  }
  System.out.println("Processed file: " + filename);
  Path filePath = Paths.get(tempDirectory, filename).toAbsolutePath();
  Files.copy(httpPart.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);
}

Webhookの結果は管理画面で確認

Webhookでデータが送信されたログは管理画面で確認できます。送信時のAPIキー設定など、HTTPヘッダーを編集するといった機能も用意されていますので、運用に応じて細かなカスタマイズが可能です。

Webhookログ

まとめ

メールと連携したシステムはよくあります。通常、メールサーバを立てて、その中で処理することが多いのですが、メールサーバが落ちてしまうとシステムが稼働しなくなったり、メール文面の解析が煩雑でした。Customers Mail Cloudを使えばそうした手間なくJSONで処理できて便利です。

添付ファイルまで処理対象にしたい時には、この方法を利用してください。

受信サーバ | Customers Mail Cloud