外部サービスを使って大量メールを送る - Java & Mailgun

# 前書き
外部サービスのメールAPIを使用して、
添付ファイルを送信したい！となった場合の参考に。
メール送信APIは各種存在していますが、今回はMailgunを使用しています。
メリデメの考察や登録方法などは割愛します。

なお、タイトルにある「大量メール」ですが、会員登録されたユーザにメールを送る際に使用することを想定したため「大量メール」と付けています。Mailgunは課金次第になりますが大量メールに耐えられるAPIを提供されています。無料登録だとそこまで大量というほど送信できるわけでは無いですが…。

# 環境
* Java 8
* Spring Framework
* メールサービス：MailgunAPI ([リファレンスリンク](https://documentation.mailgun.com/en/latest/api_reference.html#api-reference))

※lombokは今回は使用していません。使ってもよかったのですが…。

## リファレンスにサンプル載ってるけど？
はい。載ってます。
ただし、サンプルに使用されているJavaのHTTPライブラリの場合、メールの添付ファイルに日本語文字が含まれているとメールの受信側で文字化けしたファイル名になってしまうため、やむなくコードに起こしました。
なお、Java以外の言語のサンプルも載っていますが、そちらは試しておりません。あしからずご了承ください。

## 大まかなコードの流れ
基本的にリファレンスのサンプルを、SpringFrameworkのRestTemplateを使用したものにリファクタしたものになります。
イメージ的には、
1. Webのフォームから送信先や件名・本文を入力し、添付ファイルを付けて、送信ボタンを押す
2. Webサーバが受け取った情報をMailgunAPIへ投げる
ことを想定しており、本コードでは上記2のサンプルになります。

# コードサンプル
```java
package sample.mailgun.service;

import java.io.IOException;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.Base64Utils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;

@Service
public class MailgunFileSendSampleService {

	// logger
	private static final Logger log = LoggerFactory.getLogger(MailgunFileSendSampleSercice.class);

	private static final String mailgunDomain = "{Mailgunで登録しているドメインを設定する}";
	private static final String mailgunApiKey = "{Mailgunで登録しているAPI-Keyを設定する}";

	/**
	 * Mailgun APIを使用して添付ファイル付きメールを送信する
	 * @param files MultipartFileの配列
	 * @throws IOException ファイル操作時の例外
	 */
	public void sendMailgunAttachFile(MultipartFile[] files) throws IOException {
		/*
		  Mailgun公式サイトに載っているサンプルで使用される
		    [UniRest REST client](https://documentation.mailgun.com/en/latest/libraries.html#java)
		  では、日本語名のファイルが文字化けしたため、SpringFrameworkのRestTemplateにて実装しています。
		 */
		// Mailgun URL
		String mailgunUrl = "https://api.mailgun.net/v3/" + mailgunDomain + "/messages";

		// Mailgun Header
		HttpHeaders headers = new HttpHeaders();
		headers.setContentType(MediaType.MULTIPART_FORM_DATA);
		String auth = Base64Utils.encodeToString(("api:" + mailgunApiKey).getBytes());
		headers.add("Authorization", "Basic " + auth);

		// Mailgun body
		MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
		body.add("from", "from@" + mailgunDomain); // Mailgunで登録しているドメインのメールアドレスを設定する
		body.add("to", "to@mail.address"); // メールの送信先(To)を設定
		body.add("cc", "cc@mail.address"); // メールの送信先(CC)を設定
		body.add("bcc", "bcc@mail.address"); // メールの送信先(BCC)を設定
		body.add("subject", "メールの件名hogehoge");
		body.add("text", "メールの本文hogehoge");
		body.add("o:tag", "tagに設定したい値1"); // MailgunAPI参照
		body.add("o:tag", "tagに設定したい値2"); // MailgunAPI参照
		DateTimeFormatter format = DateTimeFormatter.ofPattern("EEE, d MMM yyyy HH:mm:ss Z", Locale.ENGLISH);
		body.add("o:deliverytime", ZonedDateTime.now().format(format)); // メールを送信する時刻。ロケールに注意
		if (files != null && files.length > 0) {
			// 添付ファイル。※今回の肝の処理。Webサーバへアップロードされたファイルの名称をそのまま添付ファイルの名称として使用するための処理を含みます。
			for (MultipartFile file : files) {
				Resource fileResource = new ByteArrayResource(file.getBytes()) {
					@Override
					public String getFilename() {
						return file.getOriginalFilename();
					}
				};
				HttpHeaders fileHeaders = new HttpHeaders();
				fileHeaders.setContentType(MediaType.valueOf(file.getContentType()));
				HttpEntity<Resource> fileEntity = new HttpEntity<>(fileResource, fileHeaders);
				body.add("attachment", fileEntity);
			}
		}

		// Mailgunへの接続
		RestTemplate restTemplate = new RestTemplate();
		HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
		try {
			ResponseEntity<MailgunResponseBean> response = restTemplate.postForEntity(
					mailgunUrl, requestEntity, MailgunResponseBean.class);

			if (!HttpStatus.OK.equals(response.getStatusCode())) {
				log.warn("Mailgunへの送信依頼に失敗しました。");
			}
			log.debug("Mailgun send response Status: " + response.getStatusCode().value());
			if (response.getBody() != null) {
				log.debug("Mailgun send response body: " + "'message':'" + response.getBody().getMessage() + "','id':'"
						+ response.getBody().getId() + "'");
			}
		} catch (RestClientException e) {
			// mailgunへの接続時に何らかの例外が出た場合。宛先不明となる同一アドレスに一定回数以上送信した場合もExceptionとなる可能性あり。
			log.error("Mailgunへの接続時にエラーが発生しました。");
			e.printStackTrace();
		}
	}

	/**
	 * MailgunのResponseパラメータ用クラス
	 */
	public class MailgunResponseBean {
	    private String message;
	    private String id;

	    public String getMessage() {
	        return message;
	    }

	    public void setMessage(String message) {
	        this.message = message;
	    }

	    public String getId() {
	        return id;
	    }

	    public void setId(String id) {
	        this.id = id;
	    }
	}
}
```