SendGrid+JavaMail+UTF-8でメールを送信する

クラウドメールサービス[SendGrid](https://sendgrid.kke.co.jp)を使ってJavaからメールを送ってみます。
SendGridを使ってメールを送る方法はいくつかあります。詳しくは[公式ブログ](https://sendgrid.kke.co.jp/blog/?p=1300)を参照してください。

- SMTP
- Web API
- マーケティングメール機能

今回はSMTPとX-SMTPAPIを組み合わせて宛先毎に内容の異なるメールを送ってみます。

# SMTP
SMTPはWeb APIに比べて細かいメール送信パラメータを指定できるのがメリットですが、それが面倒な場合、デメリットにもなります。今回はUTF-8でメールを送ります。メール送信用ライブラリは定番の[JavaMail](https://javamail.java.net/nonav/docs/api/)を使っています。

# X-SMTPAPI
名前がSMTPっぽくて紛らわしいですが、[X-SMTPAPI](https://sendgrid.kke.co.jp/blog/?p=1300)はSendGrid独自の拡張ヘッダです。これを利用するとメール送信時にSendGridの様々な機能を使うことができます。今回は、To、Substitutionを利用してメールの件名や本文内の特定のキー文字列を宛先毎に異なる値に置換してメールを送ってみます。

# とりあえず送ってみる
以下の手順で[サンプルコード](https://github.com/SendGridJP/sendgrid-smtp-java-example)をクローン、.envファイル編集、実行してください。
Java 1.8で動作確認していますが1.6くらいでもたぶん動くような気がします。

```text
git clone https://github.com/SendGridJP/sendgrid-smtp-java-example.git
cd sendgrid-smtp-java-example
cp .env.example .env
# .envファイルを編集してください
# src/main/java/com/github/sendgridjp/JavaMailMultipartExample.javaの定数CHARSETとENCODEを下のコードサンプルのように"UTF-8"と"base64"に編集してください
gradle build
./run-multipart.sh
```

### .envファイルの編集
```text
SENDGRID_USERNAME=SendGridユーザ名
SENDGRID_PASSWORD=SendGridパスワード
TOS=hoge@hogehoge.com,fuga@fugafuga.com,piyo@piyopiyo.com,hogera@hogera.com
NAMES=名前1,名前2,名前3,名前4
FROM=you@youremail.com
```
SENDGRID_USERNAME:SendGridのユーザ名を指定してください。
SENDGRID_PASSWORD:SendGridのパスワードを指定してください。
TOS:宛先をカンマ区切りで指定してください。
NAMES:宛先毎の宛名をカンマ区切りで指定してください。
FROM:送信元アドレスを指定してください。

指定したアドレスにメールは届きましたか？

# コード


```java:JavaMailMultipartExample.java
package com.github.sendgridjp;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.file.Path;
import java.nio.file.FileSystems;
import java.nio.file.Files;

import java.util.Properties;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.Multipart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;
import javax.mail.internet.InternetAddress;
import javax.mail.util.ByteArrayDataSource;

import com.sendgrid.smtpapi.SMTPAPI;

public class JavaMailMultipartExample {
  // 設定情報
  static final String USERNAME = System.getenv("SENDGRID_USERNAME");
  static final String PASSWORD = System.getenv("SENDGRID_PASSWORD");
  static final String[] TOS    = System.getenv("TOS").split(",");
  static final String[] NAMES  = System.getenv("NAMES").split(",");
  static final String FROM     = System.getenv("FROM");
  static final String CHARSET  = "UTF-8"; // "ISO-2022-JP";
  static final String ENCODE   = "base64"; // "7bit"; // "quoted-printable";

  public static void main(String[] args)
  throws IOException, MessagingException, UnsupportedEncodingException {
    // SMTP接続情報
    Properties props = new Properties();
    props.put("mail.transport.protocol", "smtp");
    props.put("mail.smtp.host", "smtp.sendgrid.net");
    props.put("mail.smtp.port", 587);
    props.put("mail.smtp.auth", "true");
    Authenticator auth = new SMTPAuthenticator();
    Session mailSession = Session.getDefaultInstance(props, auth);
    mailSession.setDebug(true); // console log for debug

    // メッセージの構築
    MimeMessage message = new MimeMessage(mailSession);

    // ダミーの宛先(X-SMTPAPIの宛先が優先される)
    message.addRecipient(Message.RecipientType.TO, new InternetAddress(FROM));

    // From
    message.setFrom(FROM);

    // Subject
    message.setSubject("こんにちはSendGrid", CHARSET);

    // Body
    // Alternative part
    MimeMultipart altPart = new MimeMultipart();
    altPart.setSubType("alternative");
    // Text part
    String body = "こんにちは、nameさん\r\nようこそ〜テキストメールの世界へ！";
    MimeBodyPart textBodyPart = new MimeBodyPart();
    textBodyPart.setText(body, CHARSET, "plain");
    textBodyPart.setHeader("Content-Transfer-Encoding", ENCODE);
    altPart.addBodyPart(textBodyPart);
    // Html part
    String htmlBody =
      "<html>" +
      "<body bgcolor=\"#d9edf7\" style=\"background-color: #d9edf7;\">" +
      "こんにちは、nameさん<br>ようこそ〜HTMLメールの世界へ！<br>" +
      "<img src=\"cid:123@456\">" +
      "</body></html>";
    MimeBodyPart htmlBodyPart = new MimeBodyPart();
    htmlBodyPart.setText(htmlBody, CHARSET, "html");
    htmlBodyPart.setHeader("Content-Transfer-Encoding", ENCODE);
    altPart.addBodyPart(htmlBodyPart);
    // Alternative part
    MimeBodyPart altBodyPart = new MimeBodyPart();
    altBodyPart.setContent(altPart);
    // Attachment part
    MimeBodyPart attachmentPart = getMimeBodyPart(
      "./logo.gif", "logo.gif", "image/gif", "123@456"
    );
    altPart.addBodyPart(attachmentPart);

    message.setContent(altPart);

    // X-SMTPAPIヘッダ
    String smtpapi = createSmtpapi(TOS, NAMES);
    smtpapi = MimeUtility.encodeText(smtpapi);
    message.setHeader("X-SMTPAPI", MimeUtility.fold(76, smtpapi));

    // 送信
    mailSession.getTransport().send(message);
  }

  // FileからMimeBodyPartを生成
  private static MimeBodyPart getMimeBodyPart(
    String path, String name, String type, String cid
  )
  throws IOException, MessagingException, UnsupportedEncodingException {
    MimeBodyPart attachment = new MimeBodyPart();
    byte[] bytes = Files.readAllBytes(FileSystems.getDefault().getPath(path));
    DataSource dataSource = new ByteArrayDataSource(bytes, type);
    DataHandler dataHandler = new DataHandler(dataSource);
    attachment.setDataHandler(dataHandler);
    attachment.setFileName(MimeUtility.encodeWord(name));
    attachment.setContentID("<" + cid + ">");
    attachment.setDisposition(MimeBodyPart.ATTACHMENT);
    return attachment;
  }

  // X-SMTPAPIヘッダに設定する値の生成
  private static String createSmtpapi(String[] tos, String[] names) {
    SMTPAPI smtpapi = new SMTPAPI();
    smtpapi.setTos(tos);
    smtpapi.addSubstitutions("name", names);
    smtpapi.addCategory("category1");
    return smtpapi.rawJsonString();
  }

  // SMTP
  private static class SMTPAuthenticator extends javax.mail.Authenticator {
    public PasswordAuthentication getPasswordAuthentication() {
      return new PasswordAuthentication(USERNAME, PASSWORD);
    }
  }
}
```