Spring boot2からメールを送ろう

# はじめに
Java(Kotlin)からメールを送るといえば[javamail](https://javaee.github.io/javamail/)を使用している方が多いかと思いますが、springにもメール機能があるので、こちらの機能について調べました。

ソースのパッケージにdatabasetestとありますが、別の奴の使いまわしをしているだけなので気にしないでください。

# 環境
Kotlin
Spring-boot 2.0.1.RELEASE

SMTPサーバーとして[smtp4dev](https://github.com/rnwood/smtp4dev)を使っています。
[FakeSMTP](http://nilhcem.com/FakeSMTP/download.html)の方がシンプルなのですが、本格的な使い方として少し足りない部分があり断念しました。
シンプルで好きなんですけどね。

# Dependency
今回はMavenでやるので、pom.xmlに以下を追加します。

``` pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>
```

# まずはシンプルなメールを送ってみる
とりあえずシンプルなテキストメールを送ってみます。

``` application.properties
spring.mail.host=localhost
spring.mail.port=25
```

``` kotlin
package com.tasogarei.databasetest.contorller

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.ResponseEntity
import org.springframework.mail.MailException
import org.springframework.mail.MailSender
import org.springframework.mail.SimpleMailMessage
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.ResponseBody

@Controller
class MailController {
    @Autowired
    lateinit var mailSender : MailSender

    @ResponseBody
    @GetMapping("/mail")
    fun sendmail() : ResponseEntity<String> {
        var message = SimpleMailMessage().apply {
            setFrom( "test@test.jp")
            setTo("tasogarei34@gmail.com")
            setSubject("test subject")
            setText("test body")
        }
        try {
            mailSender.send(message)
        } catch (e : MailException) {
            // do not something
        }
        return ResponseEntity.ok().build()
    }
}
```

たったこれだけで問題ありません。
SmtpAuthにも対応しているため、いくつかのプロパティを追加するだけでそれらを実現することが可能となっています。
MTAをGmailに変更して飛ばしたいみたいなことも簡単に可能です。
ただし、[こちら](https://myaccount.google.com/u/1/lesssecureapps)をしておかないとアプリケーションから飛ばせないのでご注意ください。
簡単に外部に飛ばせたり出来るようになってしまっているので、あんまりこの設定を使ってメール飛ばす確認はしない方が良いとは思います。
まぁ、自前のMTAは設定するのが面倒くさいのでしょうがない面もありますが、smtp4devならSmtpAuthも出来るので、そちらを使用してテストしましょう。

一応設定だけ載せておきます。

``` application.properties
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username={your mail address}
spring.mail.password={your password}
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
```
# Beanにテンプレートを登録して送信
SimpleMailMessageはBean登録可能なので、テンプレートとして登録して可変箇所だけ他で設定して送信することも可能となっています。
分かりづらいので、使わない方が良い気はします。

``` application.properites
spring.mail.host=localhost
spring.mail.port=25
```

``` kotlin
package com.tasogarei.databasetest.config

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.mail.SimpleMailMessage
import org.springframework.stereotype.Component

@Component
@Configuration
class MailConfig {

    @Bean("templateMessage")
    fun template() : SimpleMailMessage {
        var template = SimpleMailMessage()
        template.setFrom("tasogarei34@gmail.com")
        template.setTo("test@test.jp")
        template.setSubject("test subject")
        template.setText("test body")
        return template
    }
}
```

``` kotlin
package com.tasogarei.databasetest.contorller

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.ResponseEntity
import org.springframework.mail.MailException
import org.springframework.mail.MailSender
import org.springframework.mail.SimpleMailMessage
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.ResponseBody

@Controller
class MailController {
    @Autowired
    lateinit var mailSender : MailSender

    @Autowired
    lateinit var templateMessage : SimpleMailMessage

    @ResponseBody
    @GetMapping("/mail")
    fun sendmail() : ResponseEntity<String> {
        try {
            mailSender.send(templateMessage)
        } catch (e : MailException) {
            println(e.message)
        }
        return ResponseEntity.ok().build()
    }
}
```

テンプレート登録についてはBeanを複数個登録すればその分作成可能となっています。

``` kotlin
package com.tasogarei.databasetest.config

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Primary
import org.springframework.mail.SimpleMailMessage
import org.springframework.stereotype.Component

@Component
@Configuration
class MailConfig {

    @Bean("templateMessage")
    fun template() : SimpleMailMessage {
        var template = SimpleMailMessage()
        template.setFrom("tasogarei34@gmail.com")
        template.setTo("test@test.jp")
        template.setSubject("test subject")
        template.setText("test body")
        return template
    }

    @Bean("template2Message")
    fun template2() : SimpleMailMessage {
        var template = SimpleMailMessage()
        template.setFrom("tasogarei34@gmail.com")
        template.setTo("test@test.jp")
        template.setSubject("test subject2")
        template.setText("test body2")
        return template
    }
}
```

``` kotlin
package com.tasogarei.databasetest.contorller

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.ResponseEntity
import org.springframework.mail.MailException
import org.springframework.mail.MailSender
import org.springframework.mail.SimpleMailMessage
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.ResponseBody

@Controller
class MailController {
    @Autowired
    lateinit var mailSender : MailSender

    @Autowired
    lateinit var templateMessage : SimpleMailMessage

    @Autowired
    lateinit var template2Message : SimpleMailMessage

    @ResponseBody
    @GetMapping("/mail")
    fun sendmail() : ResponseEntity<String> {
        try {
            mailSender.send(templateMessage)
        } catch (e : MailException) {
            println(e.message)
        }
        return ResponseEntity.ok().build()
    }

    @ResponseBody
    @GetMapping("/mail2")
    fun sendmail2() : ResponseEntity<String> {
        try {
            mailSender.send(template2Message)
        } catch (e : MailException) {
            println(e.message)
        }
        return ResponseEntity.ok().build()
    }
}
```

# HTMLメールを送る
今のままではリッチなコンテンツを送る事が出来ないので、HTMLを送ってみようと思います。
送るのは凄く簡単ですが、Textのみとは少し違いがあります。

変更点としては
・`MailSender`ではなく`JavaMailSender`を使って送信する
・`SimpleMailMessage`ではなく`MimeMessagePreparator`を実装してメッセージを作成する
という2点があります。
`MimeMessagePreparator`は`MimeMessageHelper`を使うことでとてつもなく簡単に値が設定できるHelperクラスがあるので、これを使うと良いです。
Kotlinの実装は特に楽を感じられてすっきりした実装が可能となっていて良い感じです。

application.propertiesに書く内容は変更ないので、ここでは割愛します。

``` kotlin
package com.tasogarei.databasetest.contorller

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.ResponseEntity
import org.springframework.mail.MailException
import org.springframework.mail.javamail.JavaMailSender
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.ResponseBody
import javax.mail.internet.InternetAddress
import org.springframework.mail.javamail.MimeMessageHelper

@Controller
class MailController {
    @Autowired
    lateinit var mailSender : JavaMailSender

    @ResponseBody
    @GetMapping("/mail")
    fun sendmail() : ResponseEntity<String> {
        try {
            mailSender.send({ mimeMessage ->
                MimeMessageHelper(mimeMessage, true, "UTF-8").apply {
                    setTo("tasogarei@gmail.com")
                    setFrom(InternetAddress("test@test.jp"))
                    setSubject("test subject")
                    setText("text part", "<html><body>html</body></html>")
                }
            })
        } catch (e : MailException) {
            println(e.message)
        }
        return ResponseEntity.ok().build()
    }
}
```

色んな面倒を飛び越えてもう本当に楽です。
ちなみに、添付ファイルも入れたい場合は`MimeMessageHelper`にある`addAttachment()`に指定すると添付ファイルとして扱ってくれます。
引数は色々と渡せるのですが、とりあえず`/src/main/resource`配下にあるファイルを添付する例をのせておきます。

``` kotlin
addAttachment("test.txt",DefaultResourceLoader().getResource("classpath:test.txt").file )
```

# envelop fromの付与
業務上、envelope fromとfromが違う場合がありますが、それを行う方法です。
簡単に設定するクラスとして`SMTPMessage`があるので、これを使用します。
`MimeMessage`から変換可能になっているため、`Helper`を使って`MimeMessage`を作成して変換、設定を経て送信という流れで実現します。

ちなみに`Header`も`Helper`では設定出来ないようなので、カスタムヘッダーを行いたい場合も`MimeMessage`に変換して設定してあげる必要があるようです。
なぜさせてくれないのかがさっぱり分からないです・・・

Meesage-IDについては`updateMessageID()`のOverrideが必要となるため、やるなら`MimeMessage`の継承して実装するというのは変わってません。

``` kotlin
package com.tasogarei.databasetest.contorller

import com.sun.mail.smtp.SMTPMessage
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.ResponseEntity
import org.springframework.mail.MailException
import org.springframework.mail.javamail.JavaMailSender
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.ResponseBody
import javax.mail.internet.InternetAddress
import org.springframework.mail.javamail.MimeMessageHelper

@Controller
class MailController {
    @Autowired
    lateinit var mailSender : JavaMailSender

    @ResponseBody
    @GetMapping("/mail")
    fun sendmail() : ResponseEntity<String> {
        var messageHelper = MimeMessageHelper(mailSender.createMimeMessage(), true, "UTF-8").apply {
            setTo("tasogarei@gmail.com")
            setFrom(InternetAddress("test@test.jp"))
            setSubject("test subject")
            setText("text part", "<html><body>html</body></html>")
        }
        var message = SMTPMessage(messageHelper.mimeMessage).apply {
            envelopeFrom = "envelop@test.jp"
        }
        try {
            mailSender.send(message)
        } catch (e : MailException) {
            println(e.message)
        }
        return ResponseEntity.ok().build()
    }
}
```

# 最後に
未だにJavaMailも開発が行われており積極的に変更する理由はありませんが、Springを使っているのであれば、楽にMultiPartを表現できるSpringMailを使用するという選択肢は大いにありという感想です。
見た限りではJavMailと動作がそんなに変わらなさそうにも見えるので、乗り換えも楽そうではあります。