MailFlute

DBFluteプロジェクトが提供するライブラリ MailFlute のページ。

MailFluteとは?

MailFlute は、メールテンプレート機能を備えたシンプルなメール送信ライブラリです。

DBFlute の FreeGen と組み合わせて利用することで、タイプセーフでメールパラメーターを指定できる、堅いツールになります。 DBFluteライクなメール送信ライブラリと言えるでしょう。

1. メールテンプレートを書く
パラメータコメント形式でテンプレートを作成、拡張子は.dfmail
2. FreeGenを叩く
FreeGenがメールテンプレートを読み込んで、クラスを自動生成
3. タイプセーフ実装
自動生成されたクラスを使って、タイプセーフにメール送信の実装

MailFluteのアーキテクチャ

MailFlute Architecture

LastaFluteでの利用例

LastaFluteとの組み合わせが、MailFlute利用の典型例となります。(ドキュメントも、それを前提に)

まず、src/main/resources/mail配下に、dfmailのメールテンプレートを書きます。 外だしSQLに非常によく似たパラメータコメント形式 (pmfile形式) で書きます。

e.g. 新しい会員の登録を想定したメールテンプレート @welcome_member.dfmail
/*
 [New Member's Registration]
 The member will be formalized after click.
*/
subject: Welcome to your sign up, /*pmb.memberName*/
>>>
Hello, /*pmb.memberName*/

How are you?
/*IF pmb.birthdate != null*/
Wonderful birthdate! /*pmb.birthdate*/
/*END*/

Delivery Address: /*pmb.address:orElse('none')*/

Thanks

そして、FreeGenを叩き...

e.g. Manageで 12 (freegen) を叩く @Command
...$ sh manage.sh 12

mylasta.mail配下に自動生成されたクラスで実装します。 クラス名は、テンプレートファイルをキャメルケースにしたものになります。(welcome_member.dfmail なら WelcomeMemberPostcard)

e.g. 自動生成されたクラスで実装 @Java
WelcomeMemberPostcard.droppedInto(postbox, postcard -> {
    postcard.setFrom("from@example.com", LABELS_OFFICE_MAIL);
    postcard.addTo("to@example.com");
    postcard.setMemberName("sea");
    postcard.setBirthdate(birthdate);
    postcard.addReplyTo("replyto@example.com");
});

メールテンプレート

拡張子は .dfmail で、パラメーターコメント形式で書きます。

Body Meta

コメントや subject など、メールBodyのメタ情報 (Body Meta) を >>> 区切りで定義します。

*HeaderComment
メールに関する開発用コメント、タイトルと説明がある
*Title
メールを一行で要約する開発用コメント、[...]形式で書く
*Description
メールを自由に説明する開発用コメント、Titleの下に書く
*subject:
メールの件名、文言の中で動的パラメーターが利用できる
option:
メールのオプション、HTMLメールも飛ばすなら option: +html
-- !![type] [name]!!
自動判別できないプロパティを明示的宣言をして自動生成
e.g. Body Meta の属性が勢ぞろい @welcome_member.dfmail
/*
 [New Member's Registration]
 The member will be formalized after click.
*/
subject: Welcome to your sign up, /*pmb.memberName*/
option: +html
-- !!List<Integer> seaList!!
>>>
Hello, /*pmb.memberName*/

...

ヘッダーコメント (タイトル、説明)

Title や Description は必須です。メールテンプレートには必ずタイトルと説明を入れます。 形式も決まっています。必ず /* [タイトル] 説明 */ という形式でないと、FreeGenも実行時もエラーで落ちます。

e.g. ヘッダーコメントの形式 @dfmail
/*
 [タイトル]
 説明
*/
subject: ...
...

タイトルと説明は、あくまで開発用コメントで、実装に送信されるメールの件名(subject)とは別です。

subject: (メールの件名)

さて、こちらは実際にメールされて受信者が目にするメールの件名です。

件名の中でも、パラメータコメントを使うことができます。

e.g. 件名の中で、パラメータコメント @dfmail
/*
 [タイトル]
 説明
*/
subject: Hello, /*pmb.memberName*/. How are you?
...

option: (メールのオプション)

option: +html
HTMLメールも飛ばすときに、Plainテキスト側のテンプレートの option: に指定します。
もし、Plainテキストのテンプレートファイルの welcome_member.dfmail だとしたら、HTMLメールのテンプレートファイルは、同じディレクトリに welcome_member_html.dfmail となります。
HTMLメールのテンプレートファイルには、ヘッダーを付与できません。(一行目から本文を書きます)
e.g. HTMLメールのテンプレートファイルの置き場所 @Directory
src/main/resources
 |-mail
 |  |-member
 |  |  |-welcome_member_html.dfmail // HTML text
 |  |  |-welcome_member.dfmail      // plain text
...

自動判別できないプロパティの宣言

外だしSQLのSql2Entityと同じような感じで、自動判別できないプロパティが存在するので、その場合は明示的に宣言をします。

e.g. 自動判別できないプロパティの宣言 @dfmail
...
subject: Welcome to your sign up, /*pmb.memberName*/
-- !!List<Integer> seaList!!
>>>
Hello, /*pmb.memberName*/
...

FORコメントで使うプロパティが代表的な例です。

パラメータコメント (外だしSQLライク)

DBFluteの外だしSQLと同じようなパラメータコメントが使えます。(同じようなというか、DBFluteのクラスをそのまま使っています)

SQLとは用途が違うので、若干使い方が変わります。

  • バインド変数コメントのテスト値は不要 e.g. /*pmb.sea*/
  • 埋め込み変数コメントとバインド変数コメントに違いはない (埋め込みは使わなくてOK)
  • SQLに特化した機能は使うことはない (LikeSearchとか)
  • 空文字は空文字のままキープされる (外だしSQLのParameterBeanは空文字をnull扱いしている)

orElseオプション

さらに、MailFluteでは、バインド変数コメントで値が null だったときのデフォルト値を入れることができるようになっています。 プロパティのオプションで orElse('xxx') を利用します。

e.g. 値が null だったといのデフォルト値、memberNameがなければ Unknown と表示 @dfmail
...
Hello, /*pmb.memberName:orElse('Unknown')*/

メール上で表データを表現

メールで表データのようなものを表示したい場合は、Beanを回すFORループを使います。

  • 1. テンプレートに List<XxxBean> の宣言をする
  • 2. XxxBean を Postcard と同じパッケージに作る
  • 3. テンプレートで、FORコメントでXxxBeanをループさせる

※特に Bean をという名前である必要はありません。

例えば、(src/main/resources/mail/sea配下に) sea.dfmail というメールテンプレートで、SeaRowBean という表の一行を表すBeanクラスを使うとしたら...

1. テンプレートに List<XxxBean> の宣言をする

List<SeaRowBean> 型で、リストのプロパティを宣言します。(そして、FreeGen)

e.g. SeaRowBean のリストを引数のプロパティとして宣言 @sea.dfmail
/*
 [Welcome to Sea]
 to sea's guest
*/
subject: ...
-- !!List<SeaRowBean> seaList!!
>>>
Hello,
...

2. SeaRowBean を Postcard と同じパッケージに作る

SeaRowBean を SeaPostcard の隣に作ります。(postcard が bean を import なしで参照するため)

e.g. メールで使うBeanクラスの置き場所 @Directory
src/main/java
 |-org.docksidestage
 |  |-mylasta
 |  |  |-mail
 |  |  |  |-sea
 |  |  |  |  |-SeaPostcard.java // 自動生成されたPostcard
 |  |  |  |  |-SeaRowBean.java  // 自分で作ったBeanクラス
...

※メールの中で使うBeanは、publicフィールドではなく getter/setter でプロパティ定義します。 (DBFluteのロジックを使っているので、DBFluteのポリシーになっています)

e.g. メールで使うBeanクラスはgetter/setter @Java
...
public String getShowName() {
    return showName;
}
public String getStage() {
    return stage;
}

3. テンプレートで、FORコメントでXxxBeanをループさせる

FORコメントで seaList をループさせて、それぞれのプロパティを利用します。

e.g. FORコメントでBeanをループ、ここでは単なるカンマ区切りで @sea.dfmail
Hello,

/*FOR pmb.seaList*/
/*#current.showName*/, /*#current.stage*/
/*END*/
...

UTFlute で UnitTest

UTFlute (for LastaFlute) を使っていれば、メール送信のテストがしやすくなります。

reserveMailAssertion() でアサート予約

Actの実行をする前に、reserveMailAssertion() でアサート予約をしておきます。 すると、UnitTestが終わる直前に予約したアサートが実行されます。メール本文に対するチェックが可能です。

e.g. UnitTestでメール送信のアサート @Java
// ## Arrange ##
SeaAction action = new SeaAction(); // メールを飛ばしているAction
inject(action);
reserveMailAssertion(mailData -> {
    // SeaPostcardのメールが飛んでるはず、そして、メール本文にseaが含まれているはず
    mailData.required(SeaPostcard.class).forEach(message -> {
        message.assertPlainTextContains("sea");
    });
});

// ## Act ##
JsonResponse response = action.index(); // ここでメールを飛ばしているはず

// ## Assert ##
// (もうここでは、メールに関するアサートは特になし)
...

メール送信が非同期(Async)になっていても利用できます。 (asyncされるのはメールの送信処理だけであって、アプリで送信を行ったこと自体は記録され、メール本文も保持されているので問題なく利用できる)

mailData.required(...class) にて、指定した Postcard のメールが飛んでいることをアサートしています。 そして、そのメールで送信したメッセージが送信回数分が戻り値として取得できます。

message.assertPlainTextContains("...") にて、メール本文に対するアサートができます。 様々なメソッドが用意されているので、業務的なアサートを適度にやっていくと良いでしょう。

required...()
存在することをチェックしながら、その値を戻り値で取得
assert...Contains()
そのテキストに、指定された文字列が存在するはず

メールをどのくらいアサートするか?

やり始めたらキリがないものですが、少なくとも "適切な状態で送られたかどうか?" そして "重要な動的項目が想定通りメール本文にあるか?" などをチェックすると良いでしょう。

バインド変数コメントは値がnullならエラーになり、IFコメントも文法エラーで実行時に厳しくチェックされ、メール送信処理が行われたことを保証するだけでもチェックの効果は高いです。 メールは変なのが飛んでもしまうと業務上の後始末が大変なので、UnitTestに割ける時間が少なくても、最低限のチェックだけでもしておきたいものです。

Postcard, PostOffice

TODO jflute

PostalPersonnel

TODO jflute