[Spring]メール送信機能

## はじめに
`spring-boot-starter-mail`を使用して、Gmailでメールを送信できるようにしていきます
基本的に詳細はjavadocで記載しています

## 環境
Java 11
Spring Boot 2.6.1
Gradle 2.24
spring-boot-starter-mail 2.6.1

## 依存の追加

```build.gradle
dependencies {
    implementation group: 'org.springframework.boot', name: 'spring-boot-starter-mail', version: '2.6.1'
}
```

## 設定の追加
今回はGmailを使用します

```application.properties
spring.mail.default-encoding=UTF-8
spring.mail.protocol=smtp
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=hogehoge@gmail.com
spring.mail.password=hogehoge
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
sender.mail-address=hogehoge@gmail.com
```

## HTMLテンプレートConfiguration
DIコンテナへ登録するHTMLテンプレートConfigurationを作成していきます

```HtmlTemplateConfig.java
@Configuration
public class HtmlTemplateConfig {

    @Bean
    @Primary
    public ClassLoaderTemplateResolver templateResolver() {
        ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
        templateResolver.setPrefix("templates/");
        templateResolver.setCacheable(false);
        templateResolver.setSuffix(".html");
        templateResolver.setTemplateMode("HTML5");
        templateResolver.setCharacterEncoding("UTF-8");
        return templateResolver;
    }

    @Bean
    @Primary
    public SpringTemplateEngine templateEngine() {
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.addTemplateResolver(templateResolver());
        templateEngine.addDialect(new Java8TimeDialect());
        return templateEngine;
    }

    @Bean
    @Primary
    public ThymeleafViewResolver viewResolver() {
        ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
        viewResolver.setTemplateEngine(templateEngine());
        viewResolver.setCharacterEncoding("UTF-8");
        return viewResolver;
    }

}
```

## メールテンプレートConfiguration
続いて、メールのテンプレートエンジンを使用するための、メールテンプレートに関するConfigurationを作成していきます

```MailTemplateConfig.java
@Configuration
public class MailTemplateConfig {

    private final TemplateMode templateMode = TemplateMode.TEXT;
    private final Charset charset = StandardCharsets.UTF_8;

    /**
     * メールのテンプレートエンジンを使用するための設定インスタンスを生成します。
     * @return {@link ClassLoaderTemplateResolver}
     */
    @Bean(name = "mailTemplateResolver")
    public ClassLoaderTemplateResolver mailTemplateResolver() {
        val templateResolver = new ClassLoaderTemplateResolver();
        templateResolver.setTemplateMode(templateMode);
        templateResolver.setCharacterEncoding(charset.name());
        return templateResolver;
    }

    /**
     * メールのテンプレートエンジンを使用するためのインスタンスを生成します。
     * @return {@link SpringTemplateEngine}
     */
    @Bean(name = "mailTemplateEngine")
    public SpringTemplateEngine mailTemplateEngine(@Qualifier("mailTemplateResolver") ClassLoaderTemplateResolver classLoaderTemplateResolver) {
        val templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(classLoaderTemplateResolver);
        return templateEngine;
    }

}
```

## テンプレートファイル
メール本文のテンプレートファイルを作成します

```sample.txt
タイトル
[(${title})]

内容
[(${body})]
```

## メールテンプレートのインターフェース
テンプレートに埋め込む準備をしていきます

```MailTemplate.java
public interface MailTemplate {
    /**
     * テンプレートファイルのパスを取得します。
     * @return テンプレートファイルパス
     */
    String getTemplatePath();

    /**
     * テンプレートに埋め込む変数と値のマップを取得します。
     * @return テンプレートに埋め込む変数と値のマップ
     */
    Map<String, Object> getVariables();
}
```

## メールテンプレートの実装
先程作成したテンプレートのインターフェースを実装していきます

```SampleMailTemplate.java
@RequiredArgsConstructor
public class SampleMailTemplate implements MailTemplate {
    // テンプレートパス
    private static final String TEMPLATE_PATH = "/mail/sample.txt";

    // テンプレート変数
    /** タイトル */
    private final String title;
    /** 内容 */
    private final String body;

    /**
     * {@inheritDoc}
     */
    @Override
    public String getTemplatePath() {
        return TEMPLATE_PATH;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Map<String, Object> getVariables() {
        val variables = new HashMap<String, Object>();
        variables.put("title", title);
        variables.put("body", body);
        return variables;
    }
}
```

## メール本文Factory
メール本文を作成するFactoryを作成していきます

```MailContentFactory.java
@Component
public class MailContentFactory {
    private final SpringTemplateEngine templateEngine;

    /**
     * メール本文を作成します。
     */
    public String create(MailTemplate mailTemplate) {
        return getContent(mailTemplate);
    }

    /**
     * メールのメッセージを取得します。
     * @param mailTemplate メールテンプレート
     * @return メールのメッセージ
     */
    private String getContent(MailTemplate mailTemplate) {
        return templateEngine.process(mailTemplate.getTemplatePath(), getVariables(mailTemplate));
    }

    /**
     * テンプレートのパラメータ情報を取得します。
     * @param mailTemplate メールテンプレート
     * @return {@link Context}
     */
    private Context getVariables(MailTemplate mailTemplate) {
        val context = new Context();
        context.setVariables(mailTemplate.getVariables());
        return context;
    }

    /**
     * コンストラクタ
     * @param templateEngine メールテンプレートエンジン
     */
    private MailContentFactory(@Qualifier("mailTemplateEngine") SpringTemplateEngine templateEngine) {
        this.templateEngine = templateEngine;
    }
}
```

## メールBuilder

```MailMessageBuilder.java
public class MailMessageBuilder {
    private final SimpleMailMessage mailMessage = new SimpleMailMessage();

    /**
     * 差出人を設定します。
     * @param from 差出人
     * @return {@link MailMessageBuilder}
     */
    public MailMessageBuilder from(String from) {
        this.mailMessage.setFrom(from);
        return this;
    }

    /**
     * 宛先を設定します。
     * @param to 宛先
     * @return {@link MailMessageBuilder}
     */
    public MailMessageBuilder to(String to) {
        this.mailMessage.setTo(to);
        return this;
    }

    /**
     * 宛先を設定します。
     * @param to 宛先
     * @return {@link MailMessageBuilder}
     */
    public MailMessageBuilder to(List<String> to) {
        this.mailMessage.setTo(to.toArray(new String[0]));
        return this;
    }

    /**
     * Bccを設定します。
     * @param bcc Bcc
     * @return {@link MailMessageBuilder}
     */
    public MailMessageBuilder bcc(String bcc) {
        this.mailMessage.setBcc(bcc);
        return this;
    }

    /**
     * 件名を設定します。
     * @param subject 件名
     * @return {@link MailMessageBuilder}
     */
    public MailMessageBuilder subject(String subject) {
        this.mailMessage.setSubject(subject);
        return this;
    }

    /**
     * 本文を設定します。
     * @param content 本文
     * @return {@link MailMessageBuilder}
     */
    public MailMessageBuilder content(String content) {
        this.mailMessage.setText(content);
        return this;
    }

    /**
     * ビルドしたメールを取得します。
     * @return {@link SimpleMailMessage}
     */
    public SimpleMailMessage build() {
        return mailMessage;
    }

}
```

## メール送信サービス

```MailSendRepository.java
@Repository
@RequiredArgsConstructor
public class MailSendRepository {

    @Value("${sender.mail-address}")
    private String fromEmail;

    private final MyMailSender mailSender;
    private final MailContentFactory mailContentFactory;

    /**
     * サンプルメールを送信します。
     * @param title タイトル
     * @param body 内容
     * @param toEmailAddress 送り先アドレス
     */
    @Async
    public void sendSampleMail(String title, String body, String toEmailAddress) {

        // メール内容作成
        String content = mailContentFactory.create(
                new SampleMailTemplate(
                        title,
                        body));

        // メール設定
        val mailMessage = new MailMessageBuilder()
                .from(fromEmail)
                .to(toEmailAddress)
                .subject("サンプルメールです。")
                .content(content)
                .build();

        // メール送信
        mailSender.send(mailMessage);
    }
}
```

## 最後に
ご覧いただきありがとうございます。
間違っている、気になる等ありましたら、優しく諭していただけるととても喜びます。