SendGridから携帯キャリア向けにメールを送信する方法

SendGridから携帯キャリア向けにメールを送信する方法

はじめに

「SendGridは携帯キャリアメールにも対応しているの?」よくこんなお問い合せをいただきます。

もちろん対応しています!
SendGridは米国発のサービスですが、日本のキャリアメールにも問題なくメールを届けることができます。でも、キャリアメールには様々な制限があるため、一般的なメールプロバイダに送信するのと比べて、気をつけなければいけないことが増えてしまうのも事実です。そこで今回は、キャリアメール宛てにメールを送る際に気をつけるべきポイントと、具体的な送信方法をご紹介したいと思います。

前提条件

今回、動作確認に使用した環境は以下の通りです。

  • ドコモ:F906i、SO-02F(Android 4.4.1)
  • au:W62K
  • ソフトバンク:202SH、iPhone 5s(iOS 8.3)

サンプルコードを試す前に以下の環境のセットアップを完了しておいてください。

メールの形式(テキストメールとデコメ)

キャリアメールがサポートしているメールの形式には「テキストメール」の他、いわゆる「デコメ」があります。デコメはデコメールや、デコレメールなど各社で呼び名が異なります。

  • テキストメール
    テキストメールは本文がtext/plainで構成される最もシンプルなメールです。
  • デコメ
    デコメは本文がテキストパートとHTMLパートから構成されるマルチパートメールのことを指します。パートの構造は各社ごとに仕様が異なるため、キャリアごとに構造を変えて送る必要があります。また、画像を埋め込む方法もキャリアごとに異なります。

送信方法と文字コード

SendGridではWeb APIマーケティングメール機能SMTPの3つの方法でメールを送信できます。Web APIとマーケティングメール機能は、簡単に送信することができますが、文字コードやマルチパートの構造を変更できないなどの制限があります。SMTPは送信内容をフルカスタマイズできますが、その分設定が難しくなります。テキストメールとデコメを送信する際に利用可能な方法をまとめると以下のようになります。

形式 送信方法 文字コード
テキストメール SMTP UTF-8、ISO-2022-JP、Shift_JISなど
自由に指定可能
Web API UTF-8のみ
マーケティングメール機能 UTF-8のみ
デコメ SMTP UTF-8、ISO-2022-JP、Shift_JISなど
自由に指定可能

文字化け

日本語メールを送る際、いつも問題になるのが文字コードと文字化けです。今回確認した機種ではISO-2022-JPとUTF-8いずれで送信しても文字化けせずに届きました。

一点、注意事項として、SMTP APIの置換機能(SubstitutionとSection)を件名で使用した場合、auとソフトバンクのフィーチャーフォンで文字化けが発生することを確認しました。原因としてSendGrid側で置換処理を行った際のエンコード方式にauとソフトバンクが対応していないことが考えられます。件名ではSMTPAPIの置換機能は利用しないようにしましょう。

RFC規約違反のメールアドレス

SendGridは日本の携帯キャリア向けに一部のRFC違反アドレスへのメール送信が可能です。詳しくはこちらをご覧ください。

サンプルコード

今回はJavaMailを使ってサンプルコードを実装してみました。サンプルコードの実行方法はREADMEを参照してください。依存ライブラリは以下の通りです。

詳しくはbuild.gradleを参照してください。各ライブラリのより詳しい使い方についてはリファレンスを参照してください。

テキストメールを送信するサンプルコード

テキストメールを送信するサンプルコードは以下の通りです。宛名の差し込みとカテゴリの指定のみ行ったシンプルな内容です。複数の宛先を指定して一斉送信することもできます。文字コードとエンコードを変更する場合、定数CHARSETとENCODEの値を変更してください。

package com.github.sendgridjp;

import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.Properties;
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.internet.MimeMessage;
import javax.mail.internet.MimeUtility;
import javax.mail.internet.InternetAddress;

import com.sendgrid.smtpapi.SMTPAPI;

public class JavaMailTextExample {
  // 設定情報
  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  = "ISO-2022-JP"; // "UTF-8";
  static final String ENCODE   = "7bit"; // "base64"; // "quoted-printable";

  public static void main(String[] args)
  throws MessagingException, UnsupportedEncodingException {
    // SMTP接続情報
    Properties props = new Properties();
    props.put("mail.transport.protocol", "smtps");
    props.put("mail.smtp.host", "smtp.sendgrid.net");
    props.put("mail.smtp.port", "587");
    props.put("mail.smtp.starttls.enable", "true");
    props.put("mail.smtp.starttls.required", "true");
    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
    String body = "こんにちは、nameさんrnようこそ〜テキストメールの世界へ!";
    message.setText(body, CHARSET, "plain");
    message.setHeader("Content-Transfer-Encoding", ENCODE);

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

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

  // 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();
  }

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

デコメを送信するサンプルコード

次はデコメを送信するサンプルコードです。キャリアに依存しないよう宛先ドメインごとにマルチパートの構造を切り替えています。

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 JavaMailDecoExample {
  // 設定情報
  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  = "ISO-2022-JP"; // "UTF-8";
  static final String ENCODE   = "7bit"; // "base64"; // "quoted-printable";

  public static void main(String[] args)
  throws IOException, MessagingException, UnsupportedEncodingException {
    // SMTP接続情報
    Properties props = new Properties();
    props.put("mail.transport.protocol", "smtps");
    props.put("mail.smtp.host", "smtp.sendgrid.net");
    props.put("mail.smtp.port", "587");
    props.put("mail.smtp.starttls.enable", "true");
    props.put("mail.smtp.starttls.required", "true");
    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
    // Root part
    MimeMultipart rootPart = new MimeMultipart();
    if (TOS[0].contains("@ezweb.ne.jp")) {
      rootPart.setSubType("mixed");
    } else {
      rootPart.setSubType("related");
    }
    // Alternative part
    MimeMultipart altPart = new MimeMultipart();
    altPart.setSubType("alternative");
    // Text part
    String body = "こんにちは、nameさんrnようこそ〜テキストメールの世界へ!";
    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>ようこそ〜デコメールの世界へ!<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);
    rootPart.addBodyPart(altBodyPart, 0);
    // Attachment part
    MimeBodyPart attachmentPart = getMimeBodyPart(
      "./logo.gif", "logo.gif", "image/gif", "123@456"
    );
    rootPart.addBodyPart(attachmentPart);

    message.setContent(rootPart);

    // 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 + ">");
    if (TOS[0].contains("@docomo.ne.jp")) {
      attachment.setDisposition(MimeBodyPart.INLINE);
    } else {
      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);
    }
  }
}

まとめ

いかがでしたか?今回は、SendGridを使って携帯キャリアメール向けにメールを届ける方法をご紹介しました。でも、迷惑メールフィルタ、大量送信時の挙動など、実は気をつけるべきことはまだまだあります。この辺りはまた別の機会にご紹介したいと思います。

さいごに

今回ご紹介したコードのうち、マルチパート構造の切り替えと画像ファイルの埋め込み部分はクラスメソッド様のブログ記事「Amazon SESを使って国内携帯キャリア対応のデコメールを送る」の内容を参考にさせていただきました。ありがとうございました。

参考記事