0
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?

Spring Bootからblastengineの公式Java SDKでメールを送る

0
Posted at

基幹システムや業務アプリケーションからの通知メールを、JavaMailSender で自前のSMTPサーバーに直接投げている現場は多いと思います。動いてはいるものの、到達率が安定しない、バウンスの後処理が手作業、SPF/DKIMの面倒を毎回見ている……といった悩みを抱えがちです。

blastengineには公式のJava SDKが用意されていて、これを使うと数行でメール送信が書けます。本記事では、この公式Java SDKを使ってトランザクションメール・HTMLメール・一斉配信(Bulk)を実装し、最後にSpring Bootアプリへ組み込むところまでを通しで解説します。

対象読者は、Java(Spring Boot)でメール送信を実装したい方、すでに JavaMailSender を使っていて移行を検討している方です。

自前SMTP送信のつらみと、配信サービスという選択肢

Javaでメールを送る方法として、まず思い浮かぶのが標準の javax.mail(Jakarta Mail)や、Spring Bootの JavaMailSender を使ってSMTPサーバーに直接接続する方法です。開発・検証段階では手軽ですが、実運用に乗せると次のような点が負担になってきます。

  • 到達率がメールサーバーの状態やIPレピュテーションに依存し、安定しない
  • SPF / DKIM / DMARC の設定・運用を自分たちで面倒見る必要がある
  • バウンス(宛先不明など)の検知と除外を自前で仕組み化しなければならない
  • 一斉送信時のレート制御・再送・配信ログの保存をアプリ側で実装することになる

blastengineはこのあたりを配信サービス側に寄せられるのが利点です。SDK経由でAPIに投げれば、配信ログの保存・バウンス管理・認証まわりはサービス側が引き受けてくれます。

観点 自前SMTP送信(JavaMailSender) blastengine + 公式SDK
実装量 SMTP設定・例外処理を自前で記述 SDKの数行で送信
到達率対策 自分でIP・認証を運用 サービス側で最適化
バウンス管理 自前でパース・除外 管理画面・APIで取得
配信ログ 自前で保存 配信IDで追跡可能
一斉配信 スレッド・レート制御を自作 Bulk APIで完結

blastengineの送信方式と公式Java SDKの位置づけ

blastengineには大きく2つの送信方式があります。

  1. REST APIhttps://app.engn.jp/api/v1/ にHTTPリクエストを投げる方式
  2. SMTPリレーsmtp.engn.jp:587(STARTTLS)に通常のSMTPで投げる方式(サーバーは smtp.engn.jp 固定、ポートは 25 / 587 / 2525 から選べ、SMTP AUTH を使う 587 が推奨。詳細は公式のSMTPリレー設定方法を参照)

公式Java SDKは、このうちREST APIをラップしたものです。認証トークンの生成(ログインIDとAPIキーをSHA256でハッシュ化してBase64エンコードする処理)もSDKが内部でやってくれるため、利用側はユーザー名とAPIキーを渡すだけで済みます。トークン生成の仕様を自分で実装したい場合は、別記事のblastengine API 共通仕様まとめ(Python実装例付き)を参照してください。

選択の目安は次のとおりです。

やりたいこと おすすめ
Javaから手早く実装したい 公式Java SDK
言語非依存で細かく制御したい REST API直叩き
既存のメール送信処理を最小変更で移したい SMTPリレー

SDKで使う主要クラスは以下の5つだけです。これさえ押さえれば実装できます。

クラス 役割
BEClient 初期化・認証
BETransaction 単件(トランザクション)送信
BEBulk 一斉配信(Bulk)
BEMailAddress 送信元アドレスと表示名
BEError 送信時の例外

準備:APIキーの取得とSDKの導入

APIユーザーとAPIキーを用意する

blastengineの管理画面でAPIユーザーを作成すると、ログインID(ユーザー名)APIキー が払い出されます。SDKの初期化にはこの2つを使います。

あわせて、送信に使うドメインの送信ドメイン認証(SPF / DKIM) を済ませておきましょう。ここを設定していないと、コードが正しくても到達率が落ちます。受信側での認証の見え方はSPF/DKIM/DMARCが受信側でどのように判定されているか調べてみるで解説しています。

SDKをプロジェクトに追加する(最初の関門)

ここがSpring Boot開発者にとって最初のつまずきポイントです。公式Java SDKは現状Maven Centralに公開されていません。配布されている blastengine.jar を取得し、プロジェクトに手動で組み込みます。

Gradleの場合は、libs ディレクトリにjarを置いて fileTree で取り込みます。

// build.gradle
dependencies {
    implementation(fileTree(dir: "libs", include: ["blastengine.jar"]))
}

Mavenの場合は、いったんローカルリポジトリにインストールしてから依存として宣言するのがきれいです。

mvn install:install-file \
  -Dfile=libs/blastengine.jar \
  -DgroupId=jp.blastengine \
  -DartifactId=blastengine \
  -Dversion=1.0.0 \
  -Dpackaging=jar
<!-- pom.xml -->
<dependency>
    <groupId>jp.blastengine</groupId>
    <artifactId>blastengine</artifactId>
    <version>1.0.0</version>
</dependency>

この方式には次の注意点があります。

  • 依存管理の本流(Maven Central)から外れるため、CI/CD環境でも同じjarを解決できるようにする必要があります(jarをリポジトリに含める、社内Mavenリポジトリ(Nexusなど)に登録する、といった対応)
  • チーム開発では「ローカルにだけjarがある」状態にならないよう、配置場所と手順をREADME化しておくと安全です

importは jp.blastengine パッケージから行います。

import jp.blastengine.BEClient;
import jp.blastengine.BETransaction;
import jp.blastengine.BEBulk;
import jp.blastengine.BEMailAddress;
import jp.blastengine.BEError;

まずは最小構成で1通送る(トランザクションメール)

最小構成で1通だけ送ってみます。BEClient.initialize() で認証情報を渡し、BETransaction を組み立てて send() するだけです。

// 認証(アプリ起動時に一度だけ呼べばOK)
BEClient.initialize("YOUR_USER_NAME", "YOUR_API_KEY");

BETransaction transaction = new BETransaction();
transaction.subject = "テスト送信です";
transaction.text = "blastengineの公式Java SDKから送信しています。";

// 送信元(メールアドレスと表示名)
BEMailAddress fromAddress = new BEMailAddress("info@example.com", "お知らせ");
transaction.setFrom(fromAddress);

// 宛先
transaction.addTo("user@example.jp");

try {
    Integer deliveryId = transaction.send();
    System.out.println("送信成功: deliveryId=" + deliveryId);
} catch (BEError e) {
    System.out.println("送信失敗: " + e.getMessage());
}

このコードでの注目点は次のとおりです。

  • subjecttextpublicフィールドへの直接代入で設定します。Javaのオブジェクトとしてはやや珍しい作りですが、SDKの仕様に従います
  • 送信元は BEMailAddress(メールアドレス, 表示名) で作り、setFrom() で渡します
  • 宛先は addTo() で追加します
  • send() の戻り値は 配信ID(Integer です。この値を保存しておくと、後から配信状況の照会やログ突合に使えます
  • 失敗時は BEError がスローされます。メッセージにエラー内容が入るので、ログに残しておきましょう

戻ってきたレスポンスコードの読み方(Gmail宛・Microsoft宛のエラーなど)はblastengineの配信レスポンスコード解読ガイドにまとめています。

HTMLメールとパーソナライズ

HTMLメールを送る場合は、text(プレーンテキスト版)に加えて html を設定します。両方を入れておくと、HTMLを表示できないクライアント向けのフォールバックになります。

BETransaction transaction = new BETransaction();
transaction.subject = "新機能のお知らせ";
transaction.text = "新機能をリリースしました。詳しくはWeb版でご確認ください。";
transaction.html = "<h1>新機能をリリースしました</h1>"
                 + "<p>詳しくは <a href=\"https://example.com/news\">こちら</a> をご覧ください。</p>";

transaction.setFrom(new BEMailAddress("info@example.com", "お知らせ"));
transaction.addTo("user@example.jp");
transaction.send();

HTMLメールがメーラーごとに崩れないように作るコツ(テーブルレイアウトやOutlook対策など)は本記事の範囲を超えるため割愛しますが、html に流し込むHTMLの作り込みが到達後の見え方を左右する点だけ押さえておいてください。

なお、宛先ごとに名前などを差し込むパーソナライズ配信は、後述のBulkで __name__ のような差し込み変数を使って実現します。

一斉配信(Bulk)を実装する

メルマガやお知らせの一斉送信には BEBulk を使います。トランザクションと違い、「下書きを登録 → 宛先を追加 → 確定 → 送信」 という手順を踏むのがポイントです。

宛先ごとの差し込み配信

BEBulk bulk = new BEBulk();
bulk.subject = "【__name__様】今月のお知らせ";
bulk.text = "__name__ 様\n\nいつもご利用ありがとうございます。";
bulk.html = "<h1>Hello, __name__!</h1><p>今月のお知らせです。</p>";
bulk.setFrom(new BEMailAddress("info@example.com", "お知らせ"));

// 1. 下書きを登録(配信IDが返る)
Integer deliveryId = bulk.register();

// 2. 宛先と差し込み変数を追加
Map<String, String> vars = new HashMap<>();
vars.put("name", "山田");
bulk.addTo("user1@example.jp", vars);

// 3. 宛先リストを確定
bulk.update();

// 4. 送信(即時)
bulk.send();

このコードでの注目点は次のとおりです。

  • 件名・本文・HTMLに __name__ のように __キー__ 形式で差し込み変数を書けます
  • addTo() の第2引数の Map で、宛先ごとの変数値(name山田)を渡します。Map のキーが差し込み変数名に対応します
  • フローは register()(下書き登録)→ addTo()(宛先追加)→ update()(確定)→ send()(送信) の順です。update() を忘れると追加した宛先が反映されないので注意してください

予約配信にしたい場合は、send() に送信日時(java.util.Date)を渡します。

// 翌日の同時刻に予約送信
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_MONTH, 1);
bulk.send(cal.getTime());

CSVから宛先を一括登録する

宛先が大量にある場合は、addTo() を1件ずつ呼ぶ代わりにCSVファイルからまとめて取り込めます。

BEBulk bulk = new BEBulk();
bulk.subject = "一括配信テスト";
bulk.text = "本文です。";
bulk.setFrom(new BEMailAddress("info@example.com", "お知らせ"));
bulk.register();

// CSVから宛先を取り込み、結果を受け取る(第2引数 true = エラー行は無視して有効な宛先だけ登録)
BEJob job = bulk.importFile("path/to/recipients.csv", true);
System.out.println("総件数: " + job.totalCount);
System.out.println("成功: " + job.successCount);
System.out.println("失敗: " + job.failedCount);

// 取り込んだ宛先に対して送信(CSV取り込みでは update() は呼ばない)
bulk.send();

importFile() には引数違いのオーバーロードがあり、第2引数 ignoreErrorstrue ならエラー行をスキップして有効な宛先だけ登録)、第3引数 immediatetrue なら取り込み完了と同時に即時配信)を指定できます。取り込みジョブの完了待ちはどのオーバーロードでもSDKが内部で行うため、引数で指定するものではありません。また、CSV取り込みでは宛先がサーバー側のジョブとして登録されるので、addTo() で組み立てるときと違い update() は呼びません(メモリ上の宛先リストが空のまま update() を呼ぶとSDKが No recipients を返します)。immediatetrue にした場合は取り込み完了時点で配信されるため、後続の send() も不要です。戻り値の BEJob から totalCount / successCount / failedCount を取得できるので、部分的に失敗した宛先がないかを必ずチェックしましょう。一括登録ジョブで事故らないための具体的な注意点はblastengineの宛先一括登録ジョブを"事故らせない"小技3つにまとめています。

エラーハンドリングとレート制限

送信系のメソッドは BEError をスローします。Spring Bootに組み込む際は、握りつぶさずに上位へ伝搬させ、ログとモニタリングに残すのが基本です。

try {
    Integer deliveryId = transaction.send();
    log.info("メール送信成功 deliveryId={}", deliveryId);
} catch (BEError e) {
    log.error("メール送信失敗: {}", e.getMessage(), e);
    throw new MailDeliveryException("通知メールの送信に失敗しました", e);
}

また、blastengineのAPIにはレート制限(500リクエスト/分) があります。SDK利用時も、大量にトランザクション送信をループで回すとこの上限に当たることがあります。上限を超えると HTTP 429 が返り、このときのレスポンスヘッダに X-Rate-Limit-Remaining(残り回数)と X-Rate-Limit-Retry-After-Seconds(リセットまでの秒数)が含まれます。ただしこれらのヘッダはレート制限エラー時にのみ返るうえ、Java SDK(BEClient)はレスポンスボディしか返さずヘッダを呼び出し側に渡しません。そのため「ヘッダの残り回数を見ながら調整する」ことはSDK経由ではできず、429自体は BEError としてスローされますが、リトライ秒数も取得できません。SDKでは、自分で送信間隔やリクエスト数を制御する(トークンバケットなど)か、BEError を捕捉して一定時間バックオフする形で対応します。Exponential Backoffやトークンバケットでの制御方法はトークンバケット方式でblastengineのRate Limitに対応する方法で解説しています。一斉送信は1件ずつトランザクションで送るのではなく、Bulkにまとめるとリクエスト数を抑えられます。

Spring Bootに組み込む

ここまでのコードをSpring Bootアプリに組み込みます。ポイントは2つです。認証情報を設定ファイルに外出しすることと、BEClient.initialize() を起動時に一度だけ呼ぶことです。

設定値を application.yml に外出しする

APIキーはコードに直書きせず、環境変数から注入します。

# application.yml
blastengine:
  username: ${BLASTENGINE_USER}
  api-key: ${BLASTENGINE_API_KEY}
  from-address: info@example.com
  from-name: お知らせ
@ConfigurationProperties(prefix = "blastengine")
public class BlastengineProperties {
    private String username;
    private String apiKey;
    private String fromAddress;
    private String fromName;
    // getter / setter は省略
}

@ConfigurationPropertiesScan(または @EnableConfigurationProperties)を有効にしておくと、上記のプロパティがDIできるようになります。

サービスクラスでラップする

送信処理を @Service にまとめ、初期化は @PostConstruct で一度だけ実行します。

@Service
public class BlastengineMailService {

    private final BlastengineProperties props;

    public BlastengineMailService(BlastengineProperties props) {
        this.props = props;
    }

    @PostConstruct
    public void init() {
        // アプリ起動時に一度だけ認証情報をセット
        BEClient.initialize(props.getUsername(), props.getApiKey());
    }

    /** 単件の通知メールを送る */
    public Integer sendNotification(String to, String subject, String text, String html) {
        BETransaction tx = new BETransaction();
        tx.subject = subject;
        tx.text = text;
        tx.html = html;
        tx.setFrom(new BEMailAddress(props.getFromAddress(), props.getFromName()));
        tx.addTo(to);
        try {
            return tx.send();
        } catch (BEError e) {
            throw new RuntimeException("メール送信に失敗しました: " + e.getMessage(), e);
        }
    }
}

呼び出し側は、このサービスをDIして使うだけです。

@RestController
@RequiredArgsConstructor
public class SignupController {

    private final BlastengineMailService mailService;

    @PostMapping("/signup")
    public ResponseEntity<Void> signup(@RequestBody SignupRequest req) {
        // ...ユーザー登録処理...
        mailService.sendNotification(
            req.getEmail(),
            "ご登録ありがとうございます",
            "登録が完了しました。",
            "<p>登録が完了しました。</p>"
        );
        return ResponseEntity.ok().build();
    }
}

既存の JavaMailSender から全面移行せず、「ユーザー向けの重要な通知だけblastengineに寄せ、社内向けのログ通知はSMTPのまま」といった併用から始めるのも現実的です。送信先や重要度で送信経路を切り替えると、移行リスクを抑えられます。

注意事項(ハマりどころ)

実装時・運用時にハマりやすいポイントをまとめておきます。

  • jarの解決をCI/CDでも保証する:Maven Central未掲載のため、ローカルだけにjarがある状態だとビルドが通りません。社内リポジトリ登録かリポジトリ同梱で対応します
  • APIキーをコードやGitに含めない:環境変数やSecret Managerから注入します
  • 送信ドメイン認証を先に済ませる:SPF/DKIMが未設定だと、コードが正しくても迷惑メール判定されやすくなります
  • Bulkは update() を忘れないaddTo() しただけでは確定されません。register()addTo()update()send() の順序を守ります
  • 大量送信はBulkに寄せる:トランザクションのループはレート制限に当たりやすく、リクエスト数も増えます
  • 文字コードと改行:本文の文字化けは送信前のエンコード指定が原因になりがちです。日本語本文はUTF-8で扱い、HTMLメールでは meta charset も合わせておきましょう

まとめ

  • blastengineには公式Java SDK(jp.blastengine パッケージ)があり、BEClient / BETransaction / BEBulk の3クラスを中心に数行で送信できます
  • 導入時はMaven Central未掲載のため、blastengine.jar の手動配置とCI/CDでのjar解決だけ気をつければOKです
  • 単件は BETransaction、一斉配信は BEBulkregister()addTo()update()send())。HTMLとパーソナライズ(__name__)、予約送信、CSV一括登録まで一通りそろっています
  • Spring Bootへは、認証情報を application.yml に外出しし、@PostConstruct で初期化、@Service でラップするのが扱いやすい構成です

自前SMTP送信からの移行は、まずは重要な通知メールだけを置き換えるところから始めると安全です。到達率やバウンス管理をサービス側に任せられる分、アプリ側のコードはぐっとシンプルになります。

0
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
0
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?