Kotlin
mail
spring-boot

Spring boot2からメールを送ろう

はじめに

Java(Kotlin)からメールを送るといえばjavamilを使用している方が多いかと思いますが、springにもメール機能があるので、こちらの機能について調べました。

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

環境

Kotlin
Spring-boot 2.0.1.RELEASE

SMTPサーバーとしてsmtp4devを使っています。
FakeSMTPの方がシンプルなのですが、本格的な使い方として少し足りない部分があり断念しました。
シンプルで好きなんですけどね。

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
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に変更して飛ばしたいみたいなことも簡単に可能です。
ただし、こちらをしておかないとアプリケーションから飛ばせないのでご注意ください。
簡単に外部に飛ばせたり出来るようになってしまっているので、あんまりこの設定を使ってメール飛ばす確認はしない方が良いとは思います。
まぁ、自前の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
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
    }
}
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を複数個登録すればその分作成可能となっています。

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
    }
}
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点があります。
MimeMessagePreparatorMimeMessageHelperを使うことでとてつもなく簡単に値が設定できるHelperクラスがあるので、これを使うと良いです。
Kotlinの実装は特に楽を感じられてすっきりした実装が可能となっていて良い感じです。

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

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配下にあるファイルを添付する例をのせておきます。

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

envelop fromの付与

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

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

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

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と動作がそんなに変わらなさそうにも見えるので、乗り換えも楽そうではあります。