開発本部の飯塚です。
今回は担当している製品で利用しているメール送信ライブラリを自社製のものに移行した経緯や利点などをまとめてみました。
概要
- kMailer というサービスの開発/運用を行っている
- コア機能であるメール送信のライブラリとして postal を使っていたが問題点がいくつかあった
- その問題点を解決しているライブラリが他になかったので開発し、移行した
kMailer とは?
kMailer は kintone 内に登録されているデータを利用してメール送信するというサービスです。
コア機能であるメール送信をする方法としては現状、以下2つの方法を提供しています。
- お客様のSMTPサーバーを利用したメール送信
- Google との OAuth 連携を利用したメール送信
今回は前者の「お客様のSMTPサーバーを利用したメール送信」で利用しているライブラリの移行の話です。
postal とは?
postal は Clojure でSMTPによるメール送信をしようとした場合の デファクトスタンダードと言っても過言ではないライブラリです。 SMTP の他に sendmail を使ったメール送信にも対応しています。
以下、テスト用メールサーバーを使ってメール送信をする簡単な例です。
(require '[postal.core :as postal]) (postal/send-message {:host "localhost" :port 1025} {:from "alice@example.com" :to "bob@example.com" :subject "hello" :body "postal world"})
postal の問題点
postal の API はとてもシンプルで postal.core/send-message
が提供されているのみです。
この関数に接続先のサーバー情報とメールの内容を渡せばそれだけでメール送信をしてくれます。
とても簡単で良いのですがいくつか問題がありました。
- 開発が止まっている JavaMail に依存している
- JavaMail は ver 1.6.2 のリリースを最後に Eclipse Enterprise for Java の一部となり Jakarta Mail と名前が変わり、こちらで開発が続けられています。
- JavaMail には宛先として不正なメールアドレス(例えば
bob@example.com.
のように末尾がドットで終わる)を渡しただけでもNullPointerException
を投げる問題があり、後継の Jakarta Mail を使いたいという要望がありました。- Jakarta Mail の場合
javax.mail.internet.AddressException
例外がDomain ends with dot
というメッセージと共に投げられるのでとても親切です。
- Jakarta Mail の場合
- 一度に複数のメールを送った場合のエラー時の挙動
- 複数のメールを送信する場合、単純に
doseq
でループを回しているだけなので、途中で前述のようなNullPointerException
が投げられてしまうとそれ以降のメール送信は止まってしまいます。- その上、リクエスト処理の結果は
doseq
内で捨てられてしまっているため、どこまで送信リクエストが完了したのかもわかりません。
- その上、リクエスト処理の結果は
- 複数のメールを送信する場合、単純に
- メールサーバーとの接続/切断が暗黙的
- 複数のメールを一度に送信する場合でメール単位の結果を取得しようとすると、メール単位で
postal.core/send-message
を呼び出すしかないのですが、メールサーバーとの接続/切断は関数内で閉じられてしまっています。- そのため、例えば Gmail (smtp.gmail.com) を使ったメールの一斉送信をしようとすると、メールサーバーへの接続/切断を繰り返しすぎて
Too many login attempts
エラーが簡単に発生してしまいます。
- そのため、例えば Gmail (smtp.gmail.com) を使ったメールの一斉送信をしようとすると、メールサーバーへの接続/切断を繰り返しすぎて
- 複数のメールを一度に送信する場合でメール単位の結果を取得しようとすると、メール単位で
後者2つの問題解決のためのプルリクエストは出したもののマージには至らなかったので、kMailer では postal をフォークして改修したバージョンを製品に使うという中途半端な状態が続いていました。
tarayo とは?
tarayo は postal での問題点を解決するために弊社が開発した SMTP クライアントライブラリです。 名前の由来は郵便局の木として知られるタラヨウからです。
postal との主な違いは以下の通りです。
- Jakarta Mail ベース
- SMTP のみをサポート (単一機能のみを提供)
- メールサーバーとのコネクションが明示的
以下、postal でのメール送信例を tarayo を使って書き換えたものです。
tarayo ではメールサーバーへの接続(tarayo.core/connect
)とメール送信(tarayo.core/send!
)を別々の関数として提供しているので、
postal ほど簡単ではありません。(with-open
しているだけなので十分簡単ではありますが)
(require '[tarayo.core :as tarayo]) (with-open [conn (tarayo/connect {:host "localhost" :port 1025})] (tarayo/send! conn {:from "alice@example.com" :to "bob@example.com" :subject "hello" :body "tarayo world"}))
tarayo でどう postal の問題点を解決しているか
では postal にあった問題点を tarayo ではどうやって解決しているかを簡単にまとめてみました。
- 開発が止まっている JavaMail に依存している
- tarayo は最初から Jakarta Mail ベースで開発しています。
- 一度に複数のメールを送った場合のエラー時の挙動
tarayo.core/send!
は1通のメール送信だけを扱う関数としています。- そのため複数のメールを一度に送る場合はライブラリの利用者側でループする必要はあります。
- 1通の送信だけが対象なので、結果は勿論捨てずに返しており、送信対象と結果の紐付けも容易です。
- メールサーバーとの接続/切断が暗黙的
- メールサーバーへの接続をするだけの関数として
tarayo.core/connect
を提供しています。- これにより
with-open
マクロなどを使って明示的に接続を切る必要は出てきますが、切断するタイミングを任意に決められるので一斉送信などをする場合により柔軟な対応が可能になっています。
- これにより
- メールサーバーへの接続をするだけの関数として
kMailer で利用する上での問題点の解決を第一に開発したライブラリではありますが、なかなか使い勝手の良い出来になっているかと思います。
最後に
kMailer ではすでに postal から tarayo への移行が完了しています。 平日1日で数万通のメールを送っていて、これらがすべて tarayo でカバーされるメールです。
接続先のメールサーバーはお客様の設定次第であり多岐にわたるため、postal から tarayo への移行後に接続できなくなるメールサーバーが出ないかの不安はありました。 しかし今のところそういった問題は見つかっておらず健気にメール送信処理を頑張ってくれています!
このようにある程度の規模での利用実績も積めたので、もし Clojure でメール送信をする機会がありましたら一度 tarayo を試してみていただければなと思います。
トヨクモでは一緒に働いてくれるエンジニアを募集しております。