Amazon SESを使って国内携帯キャリア対応のデコメールを送る

AWS

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

デコメール?

みなさんデコメールと聞いてどんなイメージをお持ちでしょうか?私の中では、スマホ以前のガラケーの仕組みだと思っていました。しかし、色々調べていると、そんなことはなく、今でも現役の仕組みであることが分かったのです。今回は、そんな気付きについて皆さんに共有すると共に、Amazon SESを使ったキャリアメール配信について解説をしたいと思います。

テキストメールを送る

通常、メールはテキストで送られてきます。この形式であれば、キャリアの差異も少なく、スマホでもガラケーでも受信することができます。まずは簡単なテキストメールのサンプルをご紹介します。

import java.util.Arrays;

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.ClasspathPropertiesFileCredentialsProvider;
import com.amazonaws.regions.Region;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.simpleemail.AmazonSimpleEmailService;
import com.amazonaws.services.simpleemail.AmazonSimpleEmailServiceClient;
import com.amazonaws.services.simpleemail.model.Body;
import com.amazonaws.services.simpleemail.model.Content;
import com.amazonaws.services.simpleemail.model.Destination;
import com.amazonaws.services.simpleemail.model.Message;
import com.amazonaws.services.simpleemail.model.SendEmailRequest;

public class TextMail {

	private static final String TO = "スマホのアドレス@i.softbank.jp";
	private static final String FROM = "info@akari7.net";
	private static final String BODY = "こんにちは!";
	private static final String SUBJECT = "こんにちは!";

	public static void main(String[] args) {
		AWSCredentials credentials = new ClasspathPropertiesFileCredentialsProvider().getCredentials();
		AmazonSimpleEmailService ses = new AmazonSimpleEmailServiceClient(credentials);
		Region usEast1 = Region.getRegion(Regions.US_EAST_1);
		ses.setRegion(usEast1);

		ses.sendEmail(new SendEmailRequest(FROM, new Destination(Arrays
				.asList(TO)), new Message(new Content(SUBJECT), new Body(
				new Content(BODY)))));
	}
}

テキストとHTMLのハイブリットで送る

次にテキストとHTMLのハイブリットなメールを送ります。受け取り側の環境に合わせて表示を変えることができます。

import java.util.Arrays;

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.ClasspathPropertiesFileCredentialsProvider;
import com.amazonaws.regions.Region;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.simpleemail.AmazonSimpleEmailService;
import com.amazonaws.services.simpleemail.AmazonSimpleEmailServiceClient;
import com.amazonaws.services.simpleemail.model.Body;
import com.amazonaws.services.simpleemail.model.Content;
import com.amazonaws.services.simpleemail.model.Destination;
import com.amazonaws.services.simpleemail.model.Message;
import com.amazonaws.services.simpleemail.model.SendEmailRequest;

public class HtmlMail {

	private static final String TO = "スマホのアドレス@i.softbank.jp";
	private static final String FROM = "info@akari7.net";
	private static final String SUBJECT = "こんにちは!";
	private static final String textContent = "こんにちは!";
	private static final String htmlContent = "<h1>こんにちは!</h1>";
	
	public static void main(String[] args) {
		AWSCredentials credentials = new ClasspathPropertiesFileCredentialsProvider().getCredentials();
		AmazonSimpleEmailService ses = new AmazonSimpleEmailServiceClient(credentials);
		Region usEast1 = Region.getRegion(Regions.US_EAST_1);
		ses.setRegion(usEast1);

		SendEmailRequest req = new SendEmailRequest();
		req.setSource(FROM);
		req.setDestination(new Destination(Arrays.asList(TO)));
		Body body = new Body();
		body.setText(new Content(textContent));
		body.setHtml(new Content(htmlContent));
		Message msg = new Message(new Content(SUBJECT),body);
		req.setMessage(msg);
		
		ses.sendEmail(req);
	}
}

HTMLメールの注意点

キャリアのメールアドレスにHTMLメールを送る場合、いくつかの注意点があります。

SPFは必須

以前の記事でもご紹介しましたが、送信者を認証するSPFの仕組みは必須です。また、DKIMも設定しておきましょう。

無効なリンクを付けて送ると届かない

AタグでダミーのURLを指定してしまうとキャリア側でフィルタしてしまうことがあります。

Andoird向けのメールは制限が厳しい

Android端末のキャリアメールにHTMLを送ると拒否されてしまうことが多いです。また、HTML内の画像が表示されないこともしばしば。これの解決方法については後ほどご紹介します。一方で、iPhoneは普通のパソコンメールと同じようにやり取りされるようで、キャリア側の制限がほとんどありません。

画像の形式やサイズについて

どのキャリアもメールサイズに制限を設けています。また、キャリアによってはPNG形式を受け付けていなかったりもします。どのキャリアでも使える形式にしておくことが必要です。

ガラケーは外部ドメインのリソースにアクセスしない

これは昔から言われていましたが、今になって同じような制約に引っかかるとは、、、。最近の高性能なガラケーは、HTMLメールを受信することができます。しかし、外部ドメインの画像などを表示することはできません。おそらく、パケホーダイではない人のことを考えての仕様でしょう。

インライン画像のHTMLメールを送る

スマホとガラケーで同じメールが届くようにするためにはインラインのHTMLメールを送る必要がありそうです。これは、メールに画像を添付して、HTML内から呼び出すという形式で、外部ドメインからの画像の読込みなどがありません。

コンテンツIDの名前の付け方

インラインHTMLでメールを送る際、HTML内にコンテンツIDを指定して埋め込むのですが、auの場合は、コンテンツID名に@が入っていないと送れません。そんな仕様はどこにも書いていませんw。1週間悩みましたが解決しました。

キャリア毎にメールヘッダが異なる

まず、auはルートのヘッダがmixedになります。softbankとdocomoはrelatedです。そしてその下にalternativeヘッダがあります。この中には、bodyパートとattachmentパートがあります。attachmentパートの画像は、inlineかattachmentの指定をする必要があります。これはキャリアによって異なります。

RFC違反のメールアドレス

そもそも、RFC違反のキャリアメールアドレス宛に送れるのかって件ですが、いちおうAmazon SESは送ってくれるようです。hoge.lovely.....(^_^)......@docomo.ne.jpみたいなw

キャリアと端末に依存しないインラインHTMLメールを送る例

キャリアやガラケーの様々な制約を意識したコード例を以下に書きました。

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.Properties;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;
import javax.mail.util.ByteArrayDataSource;

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.ClasspathPropertiesFileCredentialsProvider;
import com.amazonaws.regions.Region;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.simpleemail.AmazonSimpleEmailService;
import com.amazonaws.services.simpleemail.AmazonSimpleEmailServiceClient;
import com.amazonaws.services.simpleemail.model.Content;
import com.amazonaws.services.simpleemail.model.RawMessage;
import com.amazonaws.services.simpleemail.model.SendRawEmailRequest;

public class InlineMailCarrier {

	private static final String TO = "スマホのアドレス@i.softbank.jp";
	private static final String FROM = "info@akari7.net";
	private static final String SUBJECT = "こんにちは!";
	private static final String TEXT = "こんにちは!";
	private static final String HTML = "/template.html";
	private static final String IMG = "/hoge.png";

	public static void main(String[] args) throws MessagingException, IOException {
		AWSCredentials credentials = new ClasspathPropertiesFileCredentialsProvider().getCredentials();
		AmazonSimpleEmailService ses = new AmazonSimpleEmailServiceClient(credentials);
		Region usEast1 = Region.getRegion(Regions.US_EAST_1);
		ses.setRegion(usEast1);
		Session s = Session.getInstance(new Properties(), null);
		MimeMessage msg = new MimeMessage(s);

		// Sender and recipient
		msg.setFrom(new InternetAddress(FROM));
		msg.setRecipient(javax.mail.Message.RecipientType.TO, new InternetAddress(TO));

		// Subject
		msg.setSubject(SUBJECT);

		// Root
		MimeMultipart rootContainer = new MimeMultipart();
		if (TO.contains("@ezweb.ne.jp")) {
			rootContainer.setSubType("mixed");
		} else {
			rootContainer.setSubType("related");
		}

		MimeMultipart altContainer = new MimeMultipart();
		altContainer.setSubType("alternative");

		MimeBodyPart textBody = new MimeBodyPart();
		Content textContent = new Content().withData(TEXT);
		textBody.setContent(textContent.getData(), "text/plain; charset=iso-2022-jp");
		textBody.setHeader("Content-Transfer-Encoding", "7bit");
		altContainer.addBodyPart(textBody);

		// Body part
		MimeBodyPart htmlBody = new MimeBodyPart();
		File htmlFile = new File(HTML);
		InputStream in = new FileInputStream(htmlFile);
		String html = getHTMLPart(in);
		htmlBody.setContent(html, "text/html; charset=iso-2022-jp");
		altContainer.addBodyPart(htmlBody);

		MimeBodyPart altBodyPart = new MimeBodyPart();
		altBodyPart.setContent(altContainer);
		rootContainer.addBodyPart(altBodyPart, 0);

		// attachment part
		File imageFile = new File(IMG);
		InputStream input = new FileInputStream(imageFile);
		MimeBodyPart attachment = getMimeBodyPart(input, "hoge.png", "image/png", "hoge.png");
		rootContainer.addBodyPart(attachment);

		msg.setContent(rootContainer);
		msg.setHeader("MIME-Version", "1.0");
		msg.setHeader("Content-Type", rootContainer.getContentType());

		ByteArrayOutputStream bout = new ByteArrayOutputStream();
		msg.writeTo(bout);

		// Sending part
		RawMessage rm = new RawMessage();
		rm.setData(ByteBuffer.wrap(bout.toString().getBytes()));
		System.out.println(bout.toString());
		ses.sendRawEmail(new SendRawEmailRequest().withRawMessage(rm));
	}

	// get bytes from InputStream
	private static byte[] getBytes(InputStream is) {
		ByteArrayOutputStream b = new ByteArrayOutputStream();
		OutputStream os = new BufferedOutputStream(b);
		int c;
		try {
			while ((c = is.read()) != -1) {
				os.write(c);
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (os != null) {
				try {
					os.flush();
					os.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return b.toByteArray();
	}

	// get MimeBodyPart from InputStream
	private static MimeBodyPart getMimeBodyPart(InputStream is, String name, String type, String cid) 
			throws MessagingException, UnsupportedEncodingException {
		MimeBodyPart attachment = new MimeBodyPart();
		DataSource dataSource = new ByteArrayDataSource(getBytes(is), type);
		DataHandler dataHandler = new DataHandler(dataSource);
		attachment.setDataHandler(dataHandler);
		attachment.setFileName(MimeUtility.encodeWord(name));
		attachment.setContentID("<" + cid + ">");
		if (TO.contains("@docomo.ne.jp")) {
			attachment.setDisposition(MimeBodyPart.INLINE);
		} else {
			attachment.setDisposition(MimeBodyPart.ATTACHMENT);
		}

		return attachment;
	}

	//get HTML part from InputStream
	private static String getHTMLPart(InputStream is) throws IOException {
		BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
		StringBuilder sb = new StringBuilder();
		char[] b = new char[1024];
		int line;
		while (0 <= (line = reader.read(b))) {
			sb.append(b, 0, line);
		}
		is.close();
		String html = sb.toString();
		return html;
	}
}

まとめ

キャリアメールは、メールアドレスだけでは相手の端末の種類が分かりません。しかし、相手がガラケーなのかスマホなのか、また、キャリアは何なのかに関係なく、同じプログラムで送りたいはずです。今回は、相手のメールアドレスからキャリアを判別して送信ヘッダを変えたりしつつ、どの端末であったとしても同じようにHTMLメールが送れるように調整してみました。全キャリアの全端末を見た訳ではないので、実際にやってみたらおかしい動きをする部分もあると思いますが、これをベースに調整してもらえたらなと思います。

参考情報

softbank - デコレメール

au - デコレーションメール

docomo - デコメール

デコメール携帯4社MIMEマルチパート比較