8.1. E-mail送信(SMTP)

8.1.1. Overview

本節では、SMTPによるE-mailの送信方法について説明する。

本ガイドラインでは、Jakarta MailのAPIとSpring Frameworkから提供されているMail連携用コンポーネントを利用することを前提としている。

Note

説明の対象としているのはメールを送信する部分のみである。 メール送信に係る処理方式については言及していない。 (処理方式において一例を紹介している。)


8.1.1.1. Jakarta Mailについて

Jakarta Mailは、Javaでメールの送受信を行うためのAPIを提供している。 Jakarta Mailを利用することで、メール機能を容易にJavaアプリケーションに組み込むことができる。

なお、本ガイドラインでは、Spring FrameworkのMail連携用コンポーネントを利用する前提であるため、Jakarta MailのAPIについての詳細には触れていない。 Jakarta MailのAPI仕様については、API Documentationを参照されたい。

Note

メールセッション

メールセッション(Session)は、メールサーバに接続する際に必要となる情報を管理する。

メールセッションを取得するには以下のような方法がある。

  • 典型的なエンタープライズアプリケーションにおいては、Jakarta EE(Java EE)のコンテナで管理されたメールセッションをJNDI経由で取得する。

  • Tomcatの場合はリソースファクトリで定義したメールセッションをJNDI経由で取得する。

  • staticファクトリメソッドを利用してBean定義したメールセッションをDIコンテナから取得する。

  • Javaソースから直接Sessionのstaticファクトリメソッドを利用して取得する。

なお、後述するSpringのJavaMailSenderImplを使用すると、メールセッションを直接扱わずにメールサーバと接続することも可能である。

本ガイドラインでは、以下の二つの方法による実装例を紹介する。

  • メールセッションをJNDI経由で取得する方法

  • セッションを直接扱わずにJavaMailSenderImplのプロパティに接続情報を指定する方法


8.1.1.2. Spring FrameworkのMail連携用コンポーネントについて

Spring Frameworkはメール送信を行うためのコンポーネント(org.springframework.mailパッケージ)を提供している。 このパッケージに含まれるコンポーネントはメール送信に係る詳細なロジックを隠蔽し、低レベルのAPIハンドリング(Jakarta MailのAPI呼び出し)を代行する。

具体的な実装方法の説明を行う前に、Spring Frameworkが提供するメール送信用のコンポーネントがどのようにメールを送信しているかを説明する。

Constitution of Spring Mail

項番

コンポーネント

説明

(1)
アプリケーション
JavaMailSenderのメソッドを呼び出し、メールの送信依頼を行う。

* 単純なメッセージを送信する場合は、SimpleMailMessageを生成し宛先や本文を設定することでメールを送信することもできる。
(2)
JavaMailSender
アプリケーションから指定されたMimeMessagePreparator(Jakarta MailのMimeMessageを作成するためのコールバックインターフェース)を呼び出し、メール送信用のメッセージ(MimeMessage)の作成依頼を行う。

* SimpleMailMessageを使用してメッセージを送信する場合はこの処理は呼びだされない。
(3)
アプリケーション
(MimeMessagePreparator)
MimeMessageHelperのメソッドを利用して、メール送信用のメッセージ(MimeMessage)を作成する。

* SimpleMailMessageを使用してメッセージを送信する場合はこの処理は呼びだされない。
(4)
JavaMailSender
Jakarta MailのAPIを使用して、メールの送信依頼を行う。
(5)
Jakarta Mail
メールサーバへメッセージを送信する。

本ガイドラインでは、以下のインタフェースやクラスを使用してメール送信処理を実装する方法について説明する。

  • JavaMailSender
    Jakarta Mail用のメール送信インターフェース。
    Jakarta MailのMimeMessageとSpringのSimpleMailMessageの両方に対応している。
    また、Jakarta MailのSessionの管理はJavaMailSenderの実装クラスによって行われるため、メール送信処理をコーディングする際にSessionを直接扱う必要がない。
  • JavaMailSenderImpl
    JavaMailSenderインタフェースの実装クラス。
    このクラスでは、設定済みのSessionをDIする方法と、プロパティに指定した接続情報からSessionを作成する方法をサポートしている。
  • MimeMessagePreparator
    Jakarta MailのMimeMessageを作成するためのコールバックインターフェース。
    JavaMailSendersendメソッド内から呼び出される。
    MimeMessagePreparatorprepareメソッドで発生した例外はMailPreparationException(実行時例外)にラップされ再スローされる。
  • MimeMessageHelper
    Jakarta MailのMimeMessageの作成を容易にするためのヘルパークラス。
    MimeMessageHelperには、MimeMessageに値を設定するための便利なメソッドがいくつも用意されている。
  • SimpleMailMessage
    単純なメールメッセージを作成するためのクラス。
    英文のプレーンテキストメールを作成する際に使用できる。
    UTF-8等の特定のエンコード指定、HTMLメールや添付ファイル付きメールの送信、あるいはメールアドレスに個人名を付随させるといったリッチなメッセージの作成を行う際は、Jakarta MailのMimeMessageを使用する必要がある。

8.1.2. How to use

8.1.2.1. 依存ライブラリについて

Spring FrameworkのMail連携用コンポーネントを利用する場合、以下のライブラリが追加で必要となる。

上記ライブラリに対する依存関係をpom.xmlに追加する。
マルチプロジェクト構成の場合は、domainプロジェクトのpom.xml(projectName-domain/pom.xml)に追加する。
<dependencies>

    <!-- (1) -->
    <dependency>
        <groupId>com.sun.mail</groupId>
        <artifactId>jakarta.mail</artifactId>
    </dependency>

</dependencies>

項番

説明

(1)
Jakarta Mailのライブラリをdependenciesに追加する。
アプリケーションサーバ提供のメールセッションを使用する場合、<scope>providedに設定する。

Note

上記設定例は、依存ライブラリのバージョンを親プロジェクトである terasoluna-gfw-parent で管理する前提であるため、pom.xmlでのバージョンの指定は不要である。 上記の依存ライブラリはterasoluna-gfw-parentが依存しているSpring Bootで管理されている。


8.1.2.2. JavaMailSenderの設定方法

JavaMailSenderをDIするためのBean定義を行う。

Note

マルチプロジェクト構成の場合は、envプロジェクトのprojectName-env.xmlに設定することを推奨する。 なお、本ガイドラインでは、マルチプロジェクト構成を採用することを推奨している。

8.1.2.2.1. アプリケーションサーバ提供のメールセッションを使用する場合

アプリケーションサーバ提供のメールセッションを使用する場合の設定例を以下に示す。

アプリケーションサーバから提供されているメールセッション

項番

アプリケーションサーバ

参照ページ

Apache Tomcat 9.0

Apache Tomcat 9.0 User Guide(JNDI Resources HOW-TO)(JavaMail Sessions)を参照されたい。

JNDI経由で取得したメールセッションをBeanとして登録するための設定を行う。

<jee:jndi-lookup id="mailSession" jndi-name="mail/Session" /> <!-- (1) -->

項番

説明

(1)
<jee:jndi-lookup>要素のjndi-name属性に、アプリケーションサーバ提供のメールセッションのJNDI名を指定する。

次に、JavaMailSenderをBean定義する。

<!-- (1) -->
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
    <property name="session" ref="mailSession" /> <!-- (2) -->
</bean>

項番

説明

(1)
JavaMailSenderImplをBean定義する。
(2)
sessionプロパティに設定済みのメールセッションのBeanを指定する。

8.1.2.2.2. アプリケーションサーバ提供のメールセッションを使用しない場合(認証なし)

認証が必要ない場合の設定例を以下に示す。

JavaMailSenderをBean定義する。

<!-- (1) -->
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
    <property name="host" value="${mail.smtp.host}"/> <!-- (2) -->
    <property name="port" value="${mail.smtp.port}"/> <!-- (3) -->
</bean>

項番

説明

(1)
JavaMailSenderImplをBean定義する。
(2)
hostプロパティにSMTPサーバのホスト名を指定する。
この例では、プロパティファイルで定義した値(キー「mail.smtp.host」に対する値)を設定している。
(3)
portプロパティにSMTPサーバのポート番号を指定する。
この例では、プロパティファイルで定義した値(キー「mail.smtp.port」に対する値)を設定している。

Note

プロパティファイルについての詳細は、プロパティ管理 を参照されたい。

8.1.2.2.3. アプリケーションサーバ提供のメールセッションを使用しない場合(認証あり)

認証が必要な場合の設定例を以下に示す。

JavaMailSenderをBean定義する。

<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
    <property name="host" value="${mail.smtp.host}"/>
    <property name="port" value="${mail.smtp.port}"/>
    <property name="username" value="${mail.smtp.user}"/> <!-- (1) -->
    <property name="password" value="${mail.smtp.password}"/> <!-- (2) -->
    <property name="javaMailProperties">
        <props>
            <prop key="mail.smtp.auth">true</prop> <!-- (3) -->
        </props>
    </property>
</bean>

項番

説明

(1)
usernameプロパティにSMTPサーバのユーザ名を指定する。
この例では、プロパティファイルで定義した値(キー「mail.smtp.user」に対する値)を設定している。
(2)
passwordプロパティにSMTPサーバのパスワードを指定する。
この例では、プロパティファイルで定義した値(キー「mail.smtp.password」に対する値)を設定している。
(3)
javaMailPropertiesプロパティにキー「mail.smtp.auth」としてtrueを設定する。

Note

プロパティファイルについての詳細は、プロパティ管理 を参照されたい。

Tip

TLSによる接続が必要な場合、javaMailPropertiesプロパティにキー「mail.smtp.starttls.enable」としてtrueを設定する。 なお、左記のとおり指定した場合でもSMTPサーバがSTARTTLSをサポートしていない場合は平文による通信が行われる。 必要に応じてjavaMailPropertiesプロパティにキー「mail.smtp.starttls.required」としてtrueを設定することで、STARTTLSを利用できない場合にエラーとすることも可能である。


8.1.2.3. SimpleMailMessageによるメール送信方法

英文のプレーンテキストメール(エンコードの指定や添付ファイル等が不要なメール)を送信する場合は、Springが提供しているSimpleMailMessageクラスを使用する。

以下に、SimpleMailMessageクラスを使用したメール送信方法を説明する。

Bean定義例

<!-- (1) -->
<bean id="templateMessage" class="org.springframework.mail.SimpleMailMessage">
    <property name="from" value="info@example.com" /> <!-- (2) -->
    <property name="subject" value="Registration confirmation." /> <!-- (3) -->
</bean>

項番

説明

(1)
テンプレートとしてSimpleMailMessageをBean定義する。
テンプレートのSimpleMailMessageを利用するのは必須ではないが、メールメッセージで固定的な箇所(例えば送信元メールアドレス等)をテンプレート化しておくことで、メールメッセージ作成時に個別に設定する必要がなくなる。
(2)
fromプロパティにFromヘッダの内容を指定する。
(3)
subjectプロパティにSubjectヘッダの内容を指定する。

Javaクラスの実装例

@Inject
JavaMailSender mailSender; // (1)

@Inject
SimpleMailMessage templateMessage; // (2)

public void register(User user) {
    // omitted

    // (3)
    SimpleMailMessage message = new SimpleMailMessage(templateMessage);
    message.setTo(user.getEmailAddress());
    String text = "Hi "
            + user.getUserName()
            + ", welcome to EXAMPLE.COM!\r\n"
            + "If you were not an intended recipient, Please notify the sender.";
    message.setText(text);
    mailSender.send(message);

    // omitted
}

項番

説明

(1)
JavaMailSenderをインジェクションする。
(2)
テンプレートとしてBean定義したSimpleMailMessageをインジェクションする。
(3)
テンプレートのBeanを利用してSimpleMailMessageのインスタンスを生成し、Toヘッダと本文を設定して送信する。

Note

SimpleMailMessageで設定可能なプロパティ

項番

プロパティ

説明

1.
from
Fromヘッダを設定する。
2.
to
Toヘッダを設定する。
3.
cc
Ccヘッダを設定する。
4.
bcc
Bccヘッダを設定する。
5.
subject
Subjectヘッダを設定する。
6.
replyTo
Reply-Toヘッダを設定する。
7.
sentDate
Dateヘッダを設定する。
なお、明示的に設定しない場合は送信時にシステム時刻(new Date())が自動設定される。
8.
text
本文を設定する。

To、Cc、Bccに複数の宛先を設定する場合は配列にして設定する。

Warning

メールヘッダを設定する場合、メールヘッダ・インジェクションに対する考慮が必要となる。 詳細はメールヘッダ・インジェクション対策を参照されたい。


8.1.2.4. MimeMessageによるメール送信方法

英文以外のメールやHTMLメール、添付ファイルの送信を行う場合、 javax.mail.internet.MimeMessageクラスを使用する。 本ガイドラインではMimeMessageHelperクラスを使用してMimeMessageを作成する方法を推奨している。

本項では、MimeMessageHelperクラスを使用した以下のメール送信方法を説明する。

8.1.2.4.1. テキストメールの送信

MimeMessageHelperクラスを使用して、テキストメールを送信する実装例を以下に示す。

Javaクラスの実装例

@Inject
JavaMailSender mailSender; // (1)

public void register(User user) {
    // omitted

    // (2)
    mailSender.send(new MimeMessagePreparator() {

        @Override
        public void prepare(MimeMessage mimeMessage) throws Exception {
            MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,
                    StandardCharsets.UTF_8.name()); // (3)
            helper.setFrom("EXAMPLE.COM <info@example.com>"); // (4)
            helper.setTo(user.getEmailAddress()); // (5)
            helper.setSubject("Registration confirmation."); // (6)
            String text = "Hi "
                    + user.getUserName()
                    + ", welcome to EXAMPLE.COM!\r\n"
                    + "If you were not an intended recipient, Please notify the sender.";
            helper.setText(text); // (7)
        }
    });

    // omitted
}

項番

説明

(1)
JavaMailSenderをインジェクションする。
(2)
JavaMailSendersendメソッドを利用してメールを送信する。
引数にはMimeMessagePreparatorを実装した匿名内部クラスを定義する。
(3)
文字コードを指定して、MimeMessageHelperのインスタンスを生成する。
この例では、文字コードにUTF-8を指定している。
(4)
Fromヘッダの内容を設定する。
この例では、”名前 <アドレス>”の形式で設定している。
(5)
Toヘッダの内容を設定する。
(6)
Subjectヘッダの内容を設定する。
(7)
本文の内容を設定する。

Warning

メールヘッダを設定する場合、メールヘッダ・インジェクションに対する考慮が必要となる。 詳細はメールヘッダ・インジェクション対策を参照されたい。

Note

日本語のメールを送信する際、UTF-8をサポートしていないメールクライアントもサポートする必要がある場合はエンコードにISO-2022-JPを利用することも考えられる。 エンコードにISO-2022-JPを利用する際に考慮すべき事項について、ISO-2022-JPのエンコードについての考慮を参照されたい。

8.1.2.4.2. HTMLメールの送信

MimeMessageHelperクラスを使用して、HTMLメールを送信する実装例を以下に示す。

Javaクラスの実装例

@Inject
JavaMailSender mailSender; // (1)

public void register(User user) {
    // omitted

    // (2)
    mailSender.send(new MimeMessagePreparator() {

        @Override
        public void prepare(MimeMessage mimeMessage) throws Exception {
            MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,
                    StandardCharsets.UTF_8.name()); // (3)
            helper.setFrom("EXAMPLE.COM <info@example.com>"); // (4)
            helper.setTo(user.getEmailAddress()); // (5)
            helper.setSubject("Registration confirmation."); // (6)
            String text = "<html><body><h3>Hi "
                    + user.getUserName()
                    + ", welcome to EXAMPLE.COM!</h3>"
                    + "If you were not an intended recipient, Please notify the sender.</body></html>";
            helper.setText(text, true); // (7)
        }
    });

    // omitted
}

項番

説明

(1)
JavaMailSenderをインジェクションする。
(2)
JavaMailSendersendメソッドを利用してメールを送信する。
引数にはMimeMessagePreparatorを実装した匿名内部クラスを定義する。
(3)
文字コードを指定して、MimeMessageHelperのインスタンスを生成する。
この例では、文字コードにUTF-8を指定している。
(4)
Fromヘッダの内容を設定する。
この例では、”名前 <アドレス>”の形式で設定している。
(5)
Toヘッダの内容を設定する。
(6)
Subjectヘッダの内容を設定する。
(7)
本文の内容を設定する。setTextメソッドの第二引数にtrueを指定することで、Content-Typeがtext/htmlになる。

Warning

メール本文のHTMLを生成する際に外部から入力された値を使用する場合はXSS攻撃への対策を行うこと。

8.1.2.4.3. 添付ファイル付きメールの送信

MimeMessageHelperクラスを使用して、添付ファイル付きメールを送信する実装例を以下に示す。

Javaクラスの実装例

@Inject
JavaMailSender mailSender; // (1)

public void register(User user) {
    // omitted

    // (2)
    mailSender.send(new MimeMessagePreparator() {

        @Override
        public void prepare(MimeMessage mimeMessage) throws Exception {
            MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,
                    true, StandardCharsets.UTF_8.name()); // (3)
            helper.setFrom("EXAMPLE.COM <info@example.com>"); // (4)
            helper.setTo(user.getEmailAddress()); // (5)
            helper.setSubject("Registration confirmation."); // (6)
            String text = "Hi "
                    + user.getUserName()
                    + ", welcome to EXAMPLE.COM!\r\n"
                    + "Please find attached the file.\r\n\r\n"
                    + "If you were not an intended recipient, Please notify the sender.";
            helper.setText(text); // (7)
            ClassPathResource file = new ClassPathResource("doc/quickstart.pdf");
            helper.addAttachment("QuickStart.pdf", file); // (8)
        }
    });

    // omitted
}

項番

説明

(1)
JavaMailSenderをインジェクションする。
(2)
JavaMailSendersendメソッドを利用してメールを送信する。
引数にはMimeMessagePreparatorを実装した匿名内部クラスを定義する。
(3)
文字コードを指定して、MimeMessageHelperのインスタンスを生成する。
この例では、文字コードにUTF-8を指定している。
MimeMessageHelperのコンストラクタの第二引数にtrueを指定することで、マルチパートモード(デフォルトのMULTIPART_MODE_MIXED_RELATED)になる。
(4)
Fromヘッダの内容を設定する。
(5)
Toヘッダの内容を設定する。
(6)
Subjectヘッダの内容を設定する。
(7)
本文の内容を設定する。
(8)
添付ファイル名を指定して添付するファイルを設定する。
この例では、QuickStart.pdfというファイル名で、クラスパス上にあるdoc/quickstart.pdfというファイルを添付している。

8.1.2.4.4. インラインリソース付きメールの送信

MimeMessageHelperクラスを使用して、インラインリソース付きメールを送信する実装例を以下に示す。

Javaクラスの実装例

@Inject
JavaMailSender mailSender; // (1)

public void register(User user) {
    // omitted

    // (2)
    mailSender.send(new MimeMessagePreparator() {

        @Override
        public void prepare(MimeMessage mimeMessage) throws Exception {
            MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,
                    true, StandardCharsets.UTF_8.name()); // (3)
            helper.setFrom("EXAMPLE.COM <info@example.com>"); // (4)
            helper.setTo(user.getEmailAddress()); // (5)
            helper.setSubject("Registration confirmation."); // (6)
            String cid = "identifier1234";
            String text = "<html><body><img src='cid:"
                    + cid
                    + "' /><h3>Hi "
                    + user.getUserName()
                    + ", welcome to EXAMPLE.COM!\r\n</h3>"
                    + "If you were not an intended recipient, Please notify the sender.</body></html>";
            helper.setText(text, true); // (7)
            ClassPathResource res = new ClassPathResource("image/logo.jpg");
            helper.addInline(cid, res); // (8)
        }
    });

    // omitted
}

項番

説明

(1)
JavaMailSenderをインジェクションする。
(2)
JavaMailSendersendメソッドを利用してメールを送信する。
引数にはMimeMessagePreparatorを実装した匿名内部クラスを定義する。
(3)
文字コードを指定して、MimeMessageHelperのインスタンスを生成する。
この例では、文字コードにUTF-8を指定している。
MimeMessageHelperのコンストラクタの第二引数にtrueを指定することで、マルチパートモードになる。
(4)
Fromヘッダの内容を設定する。
(5)
Toヘッダの内容を設定する。
(6)
Subjectヘッダの内容を設定する。
(7)
本文の内容を設定する。setTextメソッドの第二引数にtrueを指定することで、Content-Typeがtext/htmlになる。
(8)
インラインリソースのコンテンツIDを指定してインラインリソースを設定する。
この例では、identifier1234というコンテンツIDで、クラスパス上にあるimage/logo.jpgというファイルを設定している。

Note

addInlineメソッドは、setTextメソッドの後に呼び出すこと。 そうしないと、メールクライアントがインラインリソースを正しく参照できないことがある。


8.1.2.5. メール送信時の例外について

JavaMailSendersendメソッドを利用してメール送信を行う際に発生する例外はorg.springframework.mail.MailExceptionを継承した例外である。 MailExceptionを継承した例外クラスと、それぞれの例外の発生条件について、以下の表に示す。

メール送信時の例外

項番

例外クラス

発生条件

MailAuthenticationException

認証失敗時に発生する。

MailParseException

メールメッセージのプロパティに不正な値が設定されている場合に発生する。

MailPreparationException

メールメッセージを作成中に想定外のエラーが起きた場合に発生する。 想定外のエラーとしては、例えばテンプレートライブラリで発生するエラーといったものがある。
MimeMessagePreparatorで発生した例外がMailPreparationExceptionにラップされてスローされる。

MailSendException

メールの送信エラーが起きた場合に発生する。

Note

特定の例外に対するエラー画面遷移については、 例外ハンドリング を参照されたい。


8.1.3. How to extend

8.1.3.1. テンプレートを使用したメール本文の作成方法

上で示した実装例のようにJavaソースでメール本文を直接組み立てるのは、以下の理由から推奨しない。

  • メール本文をJavaソースで組み立てるのは可読性が悪くエラーを作りやすい。

  • 表示ロジックとビジネスロジックの境界が曖昧となる。

  • メール本文のデザインを変更するために、Javaソースの修正、コンパイル、デプロイが必要になる。

よって、メール本文のデザインを定義するためにテンプレートライブラリを使用することを推奨する。 特にメール本文が複雑になるような場合はテンプレートライブラリを使用すべきである。

8.1.3.1.1. FreeMarkerを使用したメール本文の作成

本ガイドラインでは、テンプレートライブラリとしてFreeMarkerを使用する方法について説明する。

  • FreeMarkerを使用するために、依存ライブラリを設定する。

    pom.xmlの設定例

    <dependencies>
    
        <!-- (1) -->
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
        </dependency>
    
    </dependencies>
    

    項番

    説明

    (1)
    FreeMarkerのライブラリをdependenciesに追加する。

    Note

    上記設定例は、依存ライブラリのバージョンを親プロジェクトである terasoluna-gfw-parent で管理する前提であるため、pom.xmlでのバージョンの指定は不要である。 上記の依存ライブラリはterasoluna-gfw-parentが依存しているSpring Bootで管理されている。

  • freemarker.template.Configurationを生成するためのFactoryBeanをBean定義する。

    Bean定義ファイルの設定例

    <!-- (1) -->
    <bean id="freemarkerConfiguration"
        class="org.springframework.ui.freemarker.FreeMarkerConfigurationFactoryBean">
        <property name="templateLoaderPath" value="classpath:/META-INF/freemarker/" /> <!-- (2) -->
        <property name="defaultEncoding" value="UTF-8" /> <!-- (3) -->
    </bean>
    

    項番

    説明

    (1)
    FreeMarkerConfigurationFactoryBeanをBean定義する。
    (2)
    templateLoaderPathプロパティにテンプレートファイルの格納された場所を指定する。
    この例では、クラスパス上にあるMETA-INF/freemarker/ディレクトリを設定している。
    (3)
    defaultEncodingプロパティにデフォルトのエンコードを指定する。
    この例では、UTF-8を設定している。

    Note

    上記以外の設定については、FreeMarkerConfigurationFactoryBeanのJavaDocを参照されたい。 また、FreeMarker自体の設定については、FreeMarker Manual (Programmer’s Guide / The Configuration)を参照されたい。

  • メール本文のテンプレートファイルを作成する。

    テンプレートファイルの設定例

    <#escape x as x?html> <#-- (1) -->
    <html>
        <body>
            <h3>Hi ${userName}, welcome to TERASOLUNA.ORG!</h3> <#-- (2) -->
    
            <div>
                If you were not an intended recipient, Please notify the sender.
            </div>
        </body>
    </html>
    </#escape>
    

    項番

    説明

    (1)
    XSS攻撃への対策としてHTMLエスケープを行うように設定している。
    (2)
    データモデルに設定されたuserNameの値を埋め込む。

    Note

    テンプレート言語(FTL)の詳細については、FreeMarker Manual (Template Language Reference)を参照されたい。

  • テンプレートを使用してメール本文を生成し、メール送信する。

    Javaクラスの実装例

    @Inject
    JavaMailSender mailSender;
    
    @Inject
    Configuration freemarkerConfiguration; // (1)
    
    public void register(User user) {
        // omitted
    
        mailSender.send(new MimeMessagePreparator() {
    
            @Override
            public void prepare(MimeMessage mimeMessage) throws Exception {
                MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,
                        StandardCharsets.UTF_8.name());
                helper.setFrom("EXAMPLE.COM <info@example.com>");
                helper.setTo(user.getEmailAddress());
                helper.setSubject("Registration confirmation.");
                Template template = freemarkerConfiguration
                        .getTemplate("registration-confirmation.ftl"); // (2)
                String text = FreeMarkerTemplateUtils
                        .processTemplateIntoString(template, user); // (3)
                helper.setText(text, true);
            }
        });
    
        // omitted
    }
    

    項番

    説明

    (1)
    Configurationをインジェクションする。
    (2)
    ConfigurationgetTemplateメソッドを利用してTemplateを取得する。
    この例では、テンプレートファイルとして”registration-confirmation.ftl”を指定している。
    (3)
    取得したTemplateをもとに、org.springframework.ui.freemarker.FreeMarkerTemplateUtilsprocessTemplateIntoStringメソッドを利用してテンプレートから文字列を生成する。
    この例では、データモデルとしてuserNameプロパティを持つUserオブジェクト(JavaBeans)を指定している。 これにより、テンプレートファイルの${userName}の箇所にuserNameプロパティの値が埋め込まれる。

8.1.4. Appendix

8.1.4.1. ISO-2022-JPのエンコードについての考慮

日本語のメールを送信する際、送信したメールを受信するメールクライアントを限定できない場合は、エンコードにISO-2022-JPを利用することを検討する必要がある。 この理由としては、レガシーなメールクライアントがUTF-8に対応していない場合を考慮するためである。

MS932で入力された文字列に対し、エンコードにISO-2022-JPをはじめとするJIS X 0208の文字集合をベースとしたエンコードを設定した場合、 以下の表に記載する7文字において文字化けが発生する。

変換前

変換後

MS932
入力文字
入力値
(SJIS)
Unicode
(UTF-16)
Unicode
(UTF-16)
ISO-2022-JP
(JIS)
JIS X 0208
代替文字
―(全角ハイフン)
815D
U+2015
U+2014
213E
—(EM ダッシュ)
-(ハイフンマイナス)
817C
U+FF0D
U+2212
215D
−(全角マイナス)
~(全角チルド)
8160
U+FF5E
U+301C
2141
〜(波ダッシュ)
∥(平行記号)
8161
U+2225
U+2016
2142
‖(双柱)
¢(全角セント記号)
8191
U+FFE0
U+00A2
2171
¢(セント記号)
£(全角ポンド記号)
8192
U+FFE1
U+00A3
2172
£(ポンド記号)
¬(全角否定記号)
81CA
U+FFE2
U+00AC
224C
¬(否定記号)

この問題は、Unicodeを介して文字コード変換を行う際に、MS932に有りJIS X 0208に無い文字が存在するためであり、 文字化けを回避するためには、文字化けする文字について代替文字に文字コードを置き換えるなどの対処を行う必要がある。 なお、後述するx-windows-iso2022jpを使用する場合、変換処理は不要である。

以下に、変換処理の実装例を示す。

public static String convertISO2022JPCharacters(String targetStr) {

    if (targetStr == null) {
        return null;
    }

    char[] ch = targetStr.toCharArray();

    for (int i = 0; i < ch.length; i++) {
        switch (ch[i]) {

        // '―'(全角ハイフン)
        case '\u2015':
            ch[i] = '\u2014';
            break;
        // '-'(全角マイナス)
        case '\uff0d':
            ch[i] = '\u2212';
            break;
        // '~'(波ダッシュ)
        case '\uff5e':
            ch[i] = '\u301c';
            break;
        // '∥'(双柱)
        case '\u2225':
            ch[i] = '\u2016';
            break;
        // '¢'(セント記号)
        case '\uffe0':
            ch[i] = '\u00A2';
            break;
        // '£'(ポンド記号)
        case '\uffe1':
            ch[i] = '\u00A3';
            break;
        // '¬'(否定記号)
        case '\uffe2':
            ch[i] = '\u00AC';
            break;
        default:
            break;
        }
    }

    return String.valueOf(ch);
}

Note

Unicodeへのマッピング時の問題であるため、入力値の文字コードに依らず変換は必要である。 変換対象となるのは日本語を含む文字列が設定される可能性のあるヘッダおよび本文の文字列である。 日本語を含む可能性があり一般的によく使われると考えられるヘッダとしては、From、To、Cc、Bcc、Reply-To、Subjectが挙げられる。

また、エンコードにISO-2022-JPを設定する場合、以下のような範囲外となる拡張文字が文字化けする。

Out of EscapeCharacter

図-範囲外となる拡張文字の例

これらの文字は本来使用すべきではない。 もし、これらの文字を使用する必要がある場合、JVMの起動オプションとして以下のように設定することで ISO-2022-JPのエンコードが指定された場合にx-windows-iso2022jpでマッピングするように差し替えることが可能である。

-Dsun.nio.cs.map=x-windows-iso2022jp/ISO-2022-JP

Warning

x-windows-iso2022jpはISO-2022-JPの規格と異なるマッピング(MS932ベース)を含むISO-2022-JP実装である。 メールヘッダでISO-2022-JPのエンコードが指定された場合に範囲外の拡張文字を扱えるような実装となっているかはメールクライアントに依存する。 このため、x-windows-iso2022jpを使用してマッピングした場合でも、すべてのメールクライアントで確実に文字化けしないことが保証されるわけではない。

拡張文字を代替文字に変換してもよい場合、前述した7文字と同様にアプリケーションで独自に変換を行う方法も合わせて検討されたい。


8.1.4.2. JavaMailで発生していたマルチバイト文字を使用する際の不具合について

JavaMailでは、送信するメールの本文の終端がマルチバイト文字で終わっていると、終端に余計な文字(「?」や「w)」等)が出力される場合があり、従来は以下の方法で回避していた。

  • メール本文の終端文字を半角文字にする

  • メール本文の終端を改行コード(CRLF)にする

これは、シングルバイト文字とマルチバイト文字の切り替えのために付与される制御コードが付与されていなかったことに起因し、 JavaMail 1.4.4でワークアラウンドが施されたことによって、以降のバージョンでは当事象が発生しなくなった。


8.1.4.3. メールヘッダ・インジェクション対策

メールヘッダ・インジェクション攻撃が成功すると、本来意図していない宛先にメール送信され、 迷惑メール送信の踏み台に悪用される可能性がある。 メールヘッダ(Subject等)の内容に外部から入力された文字列を利用する場合、メールヘッダ・インジェクション攻撃への対策が必要となる。

例えば、MimeMessageHelpersetSubjectメソッドで以下の文字列を設定すると、Bccヘッダを追加し本文を改ざんすることが可能となる。

Notification\r\nBcc: attacker@exapmle.com\r\n\r\nManipulated body.

メールヘッダ・インジェクション攻撃への対策としては、以下のような方法が考えられる。

  • メールヘッダに設定する内容は固定値とし、外部から入力された文字列はすべてメール本文に出力する。

  • メールヘッダに設定する内容に改行文字が含まれないことをチェックする。


8.1.4.4. 処理方式

メール送信は時間のかかる処理であるため、Webアプリケーションのリクエストの中で送信処理を行うと応答時間が長くなってしまう。 このため、通常はWebアプリケーションのリクエストの中では送信処理を行わず、非同期でメール送信を行う処理方式とすることが多い。 メール送信の処理方式について詳細については言及しないが、以下に一例を示すので参考にされたい。

8.1.4.4.1. データベースまたはメッセージキューに保持されたメール情報をもとにメール送信を行う

データベースまたはメッセージキューに保持されたメール情報をもとにメール送信を行うには、以下のような機能をアプリケーションに組み込む。

  • 送信するメールの情報(宛先や本文、添付ファイル等)をデータベース(またはメッセージキュー)に登録する。

  • データベース(またはメッセージキュー)から未送信のメール情報を定期的に取得し、SMTPによるメール送信を行う。

  • 送信結果をデータベース(またはメッセージキュー)に登録する。

なお、以下の点を含めて検討する必要がある。

  • 登録されたメール情報やメール送信結果の確認方法

  • メール送信エラー時の取り扱い

Tip

メールサービスによっては、連続してメールが送信された場合に、スパムメールと判定されることがある。 左記への対策としては、同一ドメインに対し連続で送信処理を行わないように、送信順序をランダムにする方法が考えられる。


8.1.4.5. GreenMailを利用したテスト

メール送信機能をテストするためにフェイクサーバとしてGreenMailを利用する方法を紹介する。 GreenMailはライブラリとして利用する以外に、warファイルをデプロイして利用することも可能である。

GreenMailを利用したテストコードの実装例を以下に示す。

pom.xmlの設定例

<dependencies>

    <!-- (1) -->
    <dependency>
        <groupId>com.icegreen</groupId>
        <artifactId>greenmail</artifactId>
        <version>1.4.1</version>
        <scope>test</scope>
        <!-- (2) -->
        <exclusions>
            <exclusion>
                <groupId>com.sun.mail</groupId>
                <artifactId>javax.mail</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

</dependencies>

項番

説明

(1)
GreenMailのライブラリをdependenciesに追加する。
(2)
GreenMailはJakarta Mailの前身であるJavaMailに依存している。
依存ライブラリについてにおいてJakarta Mailを追加している前提で、クラス競合を防ぐためJavaMailは除外すると良い。

JUnitソースの実装例

@Inject
EmailService emailService;

@Rule
public final GreenMailRule greenMail = new GreenMailRule(
        ServerSetupTest.SMTP); // (1)

@Test
public void testSend() {

    String from = "info@example.com";
    String to = "foo@example.com";
    String subject = "Registration confirmation.";
    String text = "Hi "
            + to
            + ", welcome to EXAMPLE.COM!\r\n"
            + "If you were not an intended recipient, Please notify the sender.";
    emailService.send(from, to, subject, text);

    assertTrue(greenMail.waitForIncomingEmail(3000, 1)); // (2)

    Message[] messages = greenMail.getReceivedMessages(); // (3)

    assertNotNull(messages);
    assertEquals(1, messages.length);
    // omitted
}

項番

説明

(1)
ServerSetupTest.SMTPを指定したGreenMailRuleをルールとして設定する。
SMTPのポート番号はデフォルトで3025が使用される。
(2)
waitForIncomingEmailメソッドを利用してメールの到達を待機する。
別スレッドで非同期にメール送信が行われる際に利用する。
この例では、メール送信が非同期で行われている前提で、1通のメールが到達するまで最大3秒待機する。
(3)
getReceivedMessagesメソッドを利用してすべての受信メールを取得する。
GreenMailで送信したメールは宛先に係らず、すべてGreenMailで受信される。