JavaMailでのメール送信まとめその2

JavaMailでのメール送信まとめその2

JavaMailで携帯メール
ここでは携帯メールメッセージの作成方法。

携帯向けメールの注意点

携帯向けメールを送信するときは次の点を注意する必要があります。

  • MIME構造が特殊

 テキストメール以外の場合、携帯キャリアによってMIME構造が異なります。

  • メールアドレスがRFC違反を起こしている

 Web屋のネタ帳さんのRFCを読まなかった携帯キャリアの罪に詳しくのっています。
 対処として、メールアドレスの@より前をダブルクオートで囲みます。
 aaa..@docomo.ne.jp -> "aaa.."@docomo.ne.jp

  • SMTPの接続先ホストは、DNSで引いて取得したMXレコードを指定

 例えばドコモの場合は、「docomo.ne.jp」でDNSを引いて取得したMXレコードの「mfsmax.docomo.ne.jp」を指定する必要があります。

  • 絵文字対応

 メール本文に絵文字バイナリを含める場合、Stringクラスを使用できません。

  • メールのサイズ

 AUなど、添付ファイルのサイズの制限が厳しい端末があります。

  • SPFレコードに送信アドレスを登録

 なりすまし拒否の対応や、メール不達時にエラーメールを受け取れるようにするため、SPFを設定する必要があります。
 docomo
 au
 softbank

  • 送信流量制限

 メールの送信数/秒が大きい場合、キャリアによって送信をブロックされることがあります。
 docomoの場合は8通/秒くらいまでいけますが、softbankの場合は、2通/秒くらいに抑える必要があります。また、AUの場合は30分単位で規制をかけているようです。

  • AUの仕様がよく変わる

 いつのまにかブロックされているので注意が必要です。

 

docomo

テキストメール

docomo携帯にテキストのみのメールを送るサンプル。

text/plain
  • MimeMessage作成サンプル
Properties props = new Properties();
Session session = Session.getDefaultInstance(props);
MimeMessage msg = new MimeMessage(session);

msg.setSubject("DOCOMO向けテキストメール", "shift-jis");
msg.setFrom(new InternetAddress("from@excample.com"));
msg.setSender(new InternetAddress("sender@example.com"));
msg.setRecipient(Message.RecipientType.TO, new InternetAddress("to@excample.com"));
msg.setDataHandler(new DataHandler(new TextBinaryDataSource("絵文字バイナリを含むmail本文[:1025:][:0105:]".getBytes(), "shift-jis", "plain")));
msg.setHeader("Content-Transfer-Encoding", "base64");

return msg;
  • メール内容
From: from@excample.com
Sender: sender@example.com
To: to@excample.com
Message-ID: <11626165.01302146088049.JavaMail.hoge@hoge>
Subject: =?shift-jis?B?RE9DT01PjPyCr4Nlg0yDWINng4GBW4OL?=
MIME-Version: 1.0
Content-Type: text/plain; charset=shift-jis
Content-Transfer-Encoding: base64

ikeVto6ag2+DQ4Npg4qC8Ircgt5tYWlslnuVtvi3+XI=


■注意点

MimeMessage#setSubjectでは、絵文字バイナリを含むサブジェクトを指定できません。MimeMessage#setHeaderでMIMEエンコードされたサブジェクトを指定できますが、エンコード処理は自分で実装する必要があります。(JavaMailではMimeUtility#encodeTextでエンコードを行っていますがStringクラスしか扱えません)
サブジェクトに絵文字バイナリを含まないように運用できればいいのですが・・・。

  • メール本文

絵文字バイナリを含む場合、Stringクラスを使用できないため、MimeMessage#setTextを使用できません。そのため、DataSource経由でメール本文の文字列をバイナリで提供するようにします。手順的には、ファイルを添付する方法と同じですが、Content-TypeやContent-Transfer-Encodingの指定が異なります。ここでは、以下のようなDataSource継承クラスを作成して対応しています。

import com.ttshrk.mbemoji.Emoji;
import com.ttshrk.mbemoji.EmojiUtility;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.UnknownServiceException;
import javax.activation.DataSource;

public class TextBinaryDataSource implements DataSource {

	private byte[] text;
	private byte[] convertedText;
	private String charset;
	private String subType;

	public TextBinaryDataSource(byte[] text, String charset, String subType) throws IOException {
		this.text = text;
		this.charset = charset;
		this.subType = subType;
		initialize();
	}

	public TextBinaryDataSource(String text, String charset, String subType) throws IOException {
		this(text.getBytes(), charset, subType);
	}

	public byte[] getPlainText() {
		return text;
	}

	private void initialize() throws IOException {
		InputStream dec_in = new ByteArrayInputStream(text);
		ByteArrayOutputStream bo = null;
		OutputStream os = null;

		try {
			bo = new ByteArrayOutputStream();
			// 必要なら、文字コードや絵文字バイナリコードの変換処理を追加するとよい。
			// サンプルとして、ドコモの絵文字に変換してみる。
			os = EmojiUtility.decode(bo, charset, Emoji.Mode.DOCOMO);
			byte[] buf = new byte[2048];
			int len;

			while ((len = dec_in.read(buf)) != -1) {
				os.write(buf, 0, len);
			}
		} finally {
			if (os != null) {
				os.close();
			}
		}
		convertedText = bo.toByteArray();
	}

	@Override
	public InputStream getInputStream() throws IOException {
		InputStream is = new ByteArrayInputStream(convertedText);

		return is;
	}

	@Override
	public OutputStream getOutputStream() throws IOException {
		throw new UnknownServiceException();
	}

	@Override
	public String getContentType() {
		return "text/" + subType + "; charset=" + charset;
	}

	@Override
	public String getName() {
		return "";
	}

}
getInputStream テキストバイナリのInputStreamを返す。
getContentType MIMEヘッダ「Content-Type」で指定する文字列を返す。
HTMLメール(インライン画像なし/添付ファイルなし)

docomo携帯にHTMLメール(インライン画像なし/添付ファイルなし)を送るサンプル。

multipart/mixed
 └multipart/alternative
   ├ text/plain
   └ text/html
  • MimeMessage作成サンプル
Properties props = new Properties();
Session session = Session.getDefaultInstance(props);
MimeMessage msg = new MimeMessage(session);

msg.setSubject("DOCOMO向けHTMLメール(画像無し)送信", "shift-jis");
msg.setFrom(new InternetAddress("from@excample.com"));
msg.setSender(new InternetAddress("sender@example.com"));
msg.setRecipient(Message.RecipientType.TO, new InternetAddress("to@excample.com"));

// mixed
Multipart mixedPart = new MimeMultipart("mixed");

// alternative
MimeBodyPart alternativeBodyPart = new MimeBodyPart();
Multipart alternativePart = new MimeMultipart("alternative");
alternativeBodyPart.setContent(alternativePart);
mixedPart.addBodyPart(alternativeBodyPart);

// text mail
MimeBodyPart textBodyPart = new MimeBodyPart();
textBodyPart.setDataHandler(new DataHandler(new TextBinaryDataSource("絵文字バイナリを含むmail本文[:1025:][:0105:]".getBytes(), "shift-jis", "plain")));
textBodyPart.setHeader("Content-Transfer-Encoding", "base64");
alternativePart.addBodyPart(textBodyPart);

// html mail
MimeBodyPart htmlBodyPart = new MimeBodyPart();
htmlBodyPart.setDataHandler(new DataHandler(new TextBinaryDataSource("<HTML><BODY>絵文字バイナリを含むmail本文[:1025:][:0105:]</BODY></HTML>".getBytes(), "shift-jis", "html")));
htmlBodyPart.setHeader("Content-Transfer-Encoding", "base64");
alternativePart.addBodyPart(htmlBodyPart);

// set mixed
msg.setContent(mixedPart);

return msg;
  • メール内容
From: from@excample.com
Sender: sender@example.com
To: to@excample.com
Message-ID: <1259414.11302146088111.JavaMail.hoge@hoge>
Subject: =?shift-jis?Q?DOCOMO=8C=FC=82=AFHTM?=
 =?shift-jis?Q?L=83=81=81[=83=8B(=89=E6=91=9C=96=B3=82=B5)=91=97=90M?=
MIME-Version: 1.0
Content-Type: multipart/mixed; 
        boundary="----=_Part_0_14746332.1302146088111"

------=_Part_0_14746332.1302146088111
Content-Type: multipart/alternative; 
        boundary="----=_Part_1_8568863.1302146088111"

------=_Part_1_8568863.1302146088111
Content-Type: text/plain; charset=shift-jis
Content-Transfer-Encoding: base64

ikeVto6ag2+DQ4Npg4qC8Ircgt5tYWlslnuVtvi3+XKVtg==
------=_Part_1_8568863.1302146088111
Content-Type: text/html; charset=shift-jis
Content-Transfer-Encoding: base64

PEhUTUw+PEJPRFk+ikeVto6ag2+DQ4Npg4qC8Ircgt5tYWlslnuVtvi3+XI8L0JPRFk+PC9IVE1M
Pg==
------=_Part_1_8568863.1302146088111--

------=_Part_0_14746332.1302146088111--
HTMLメール(インライン画像あり/添付ファイル無し)

docomo携帯にHTMLメール(インライン画像あり/添付ファイルなし)を送るサンプル。

multipart/mixed
 └ multipart/related
   ├ multipart/alternative
   │ ├ text/plain
   │ └ text/html
   └ image (インライン)
  • MimeMessage作成サンプル
Properties props = new Properties();
Session session = Session.getDefaultInstance(props);
MimeMessage msg = new MimeMessage(session);

msg.setSubject("DOCOMO向けHTMLメール(インライン画像)送信", "shift-jis");
msg.setFrom(new InternetAddress("from@excample.com"));
msg.setSender(new InternetAddress("sender@example.com"));
msg.setRecipient(Message.RecipientType.TO, new InternetAddress("to@excample.com"));

// mixed
Multipart mixedPart = new MimeMultipart("mixed");

// related
MimeBodyPart relatedBodyPart = new MimeBodyPart();
Multipart relatedPart = new MimeMultipart("related");
relatedBodyPart.setContent(relatedPart);
mixedPart.addBodyPart(relatedBodyPart);

// alternative
MimeBodyPart alternativeBodyPart = new MimeBodyPart();
Multipart alternativePart = new MimeMultipart("alternative");
alternativeBodyPart.setContent(alternativePart);
relatedPart.addBodyPart(alternativeBodyPart);

// text mail
MimeBodyPart textBodyPart = new MimeBodyPart();
textBodyPart.setDataHandler(new DataHandler(new TextBinaryDataSource("絵文字バイナリを含むmail本文".getBytes(), "shift-jis", "plain")));
textBodyPart.setHeader("Content-Transfer-Encoding", "base64");
alternativePart.addBodyPart(textBodyPart);

// html mail
MimeBodyPart htmlBodyPart = new MimeBodyPart();
htmlBodyPart.setDataHandler(new DataHandler(new TextBinaryDataSource("<HTML><BODY>絵文字バイナリを含むmail本文</BODY></HTML>".getBytes(), "shift-jis", "html")));
htmlBodyPart.setHeader("Content-Transfer-Encoding", "base64");
alternativePart.addBodyPart(htmlBodyPart);

// inline image
MimeBodyPart imageBodyPart = new MimeBodyPart();
imageBodyPart.setDataHandler(new DataHandler(new FileDataSource("c:/tmp/sample.gif")));
imageBodyPart.setFileName(MimeUtility.encodeWord("sample.gif"));
imageBodyPart.setDisposition("inline");	    // inline指定しておく
imageBodyPart.setContentID("12345@12345");  // インライン画像を指定
relatedPart.addBodyPart(imageBodyPart);

// set mixed
msg.setContent(mixedPart);

return msg;
  • メール内容
From: from@excample.com
Sender: sender@example.com
To: to@excample.com
Message-ID: <28027784.21302147855674.JavaMail.hoge@hoge>
Subject: =?shift-jis?B?RE9DT01PjPyCr0hUTUyDgYFbg4sog0ODk4OJg0ODk4nmkZwpkZeQTQ==?=
MIME-Version: 1.0
Content-Type: multipart/mixed; 
        boundary="----=_Part_2_26094370.1302147855674"

------=_Part_2_26094370.1302147855674
Content-Type: multipart/related; 
        boundary="----=_Part_3_11650554.1302147855674"

------=_Part_3_11650554.1302147855674
Content-Type: multipart/alternative; 
        boundary="----=_Part_4_16270214.1302147855674"

------=_Part_4_16270214.1302147855674
Content-Type: text/plain; charset=shift-jis
Content-Transfer-Encoding: base64

ikeVto6ag2+DQ4Npg4qC8Ircgt5tYWlslnuVtg==
------=_Part_4_16270214.1302147855674
Content-Type: text/html; charset=shift-jis
Content-Transfer-Encoding: base64

PEhUTUw+PEJPRFk+ikeVto6ag2+DQ4Npg4qC8Ircgt5tYWlslnuVtjwvQk9EWT48L0hUTUw+
------=_Part_4_16270214.1302147855674--

------=_Part_3_11650554.1302147855674
Content-Type: image/gif; name=sample.gif
Content-Transfer-Encoding: base64
Content-Disposition: inline; filename=sample.gif
Content-ID: 12345@12345

R0lGODlhPAA8AJEAAP9nAf9mAP///wAAACH5BAAAAAAALAAAAAA8ADwAAAL/RI6py+0Po5w0iIuz
3rz7LyTgSJabaKYqh66u2r7yGM82i9w6fsCW1sMwVjXaLxMMNXzJUg55bCqZR6c0ea2SiqAYVis1
hrvPyxeoFafJU3MUvP5w5ZazG0313dv8vWnuEST4hpcC2HFmJxQXWGa1yJeVZ1jVIkk5Rgel6GeV
2YhGCDX5BycKicmImPY1lKpX6KZA5Lizc2jrgptLynuz6+vJ+hPW6riwiBw6LLspO6ukvLR8Qtz0
dG092rdabD04uJ3d2c0c7TreF07mfSeyvpf5mWze866NKq/6XB2Pn65PTb8266aB+wZtU71/CFEc
fFYkIkN/4irho9OOG0GLTJ3gNcoIkqO6ix8XDgQoch6/QiErumRnkmXKmQJlnpwpzZXCmzxRnTMm
UWUwYUN/1SpKSyhSjPuWMnX6AhjUakqnLquANavWrVwZFAAAOw==
------=_Part_3_11650554.1302147855674--

------=_Part_2_26094370.1302147855674--
HTMLメール(インライン画像あり/添付ファイルあり)

docomo携帯にHTMLメール(インライン画像あり/添付ファイルあり)を送るサンプル。

 multipart/mixed
 ├ multipart/related
 │ ├ multipart/alternative
 │ │ ├ text/plain
 │ │ └ text/html
 │ └ image (インライン)
 └ image (添付)
  • MimeMessage作成サンプル
Properties props = new Properties();
Session session = Session.getDefaultInstance(props);
MimeMessage msg = new MimeMessage(session);

msg.setSubject("DOCOMO向けHTMLメール(インライン画像)送信", "shift-jis");
msg.setFrom(new InternetAddress("from@excample.com"));
msg.setSender(new InternetAddress("sender@example.com"));
msg.setRecipient(Message.RecipientType.TO, new InternetAddress("to@excample.com"));

// mixed
Multipart mixedPart = new MimeMultipart("mixed");

// related
MimeBodyPart relatedBodyPart = new MimeBodyPart();
Multipart relatedPart = new MimeMultipart("related");
relatedBodyPart.setContent(relatedPart);
mixedPart.addBodyPart(relatedBodyPart);

// alternative
MimeBodyPart alternativeBodyPart = new MimeBodyPart();
Multipart alternativePart = new MimeMultipart("alternative");
alternativeBodyPart.setContent(alternativePart);
relatedPart.addBodyPart(alternativeBodyPart);

// text mail
MimeBodyPart textBodyPart = new MimeBodyPart();
textBodyPart.setDataHandler(new DataHandler(new TextBinaryDataSource("絵文字バイナリを含むmail本文".getBytes(), "shift-jis", "plain")));
textBodyPart.setHeader("Content-Transfer-Encoding", "base64");
alternativePart.addBodyPart(textBodyPart);

// html mail
MimeBodyPart htmlBodyPart = new MimeBodyPart();
htmlBodyPart.setDataHandler(new DataHandler(new TextBinaryDataSource("<HTML><BODY>絵文字バイナリを含むmail本文</BODY></HTML>".getBytes(), "shift-jis", "html")));
htmlBodyPart.setHeader("Content-Transfer-Encoding", "base64");
alternativePart.addBodyPart(htmlBodyPart);

// inline image
MimeBodyPart imageBodyPart = new MimeBodyPart();
imageBodyPart.setDataHandler(new DataHandler(new FileDataSource("c:/tmp/sample.gif")));
imageBodyPart.setFileName(MimeUtility.encodeWord("sample.gif"));
imageBodyPart.setDisposition("inline");	    // inline指定しておく
imageBodyPart.setContentID("12345@12345");  // インライン画像を指定
relatedPart.addBodyPart(imageBodyPart);

// attachment
MimeBodyPart attachBodyPart = new MimeBodyPart();
attachBodyPart.setDataHandler(new DataHandler(new FileDataSource("c:/tmp/sample.gif")));
attachBodyPart.setFileName(MimeUtility.encodeWord("sample.gif"));
attachBodyPart.setDisposition("attachment");    // attachment
mixedPart.addBodyPart(attachBodyPart);

// set mixed
msg.setContent(mixedPart);

return msg;
  • メール内容
From: from@excample.com
Sender: sender@example.com
To: to@excample.com
Message-ID: <12528990.31302148173940.JavaMail.hoge@hoge>
Subject: =?shift-jis?B?RE9DT01PjPyCr0hUTUyDgYFbg4sog0ODk4OJg0ODk4nmkZwpkZeQTQ==?=
MIME-Version: 1.0
Content-Type: multipart/mixed; 
        boundary="----=_Part_5_26680060.1302148173940"

------=_Part_5_26680060.1302148173940
Content-Type: multipart/related; 
        boundary="----=_Part_6_23910357.1302148173940"

------=_Part_6_23910357.1302148173940
Content-Type: multipart/alternative; 
        boundary="----=_Part_7_13495805.1302148173940"

------=_Part_7_13495805.1302148173940
Content-Type: text/plain; charset=shift-jis
Content-Transfer-Encoding: base64

ikeVto6ag2+DQ4Npg4qC8Ircgt5tYWlslnuVtg==
------=_Part_7_13495805.1302148173940
Content-Type: text/html; charset=shift-jis
Content-Transfer-Encoding: base64

PEhUTUw+PEJPRFk+ikeVto6ag2+DQ4Npg4qC8Ircgt5tYWlslnuVtjwvQk9EWT48L0hUTUw+
------=_Part_7_13495805.1302148173940--

------=_Part_6_23910357.1302148173940
Content-Type: image/gif; name=sample.gif
Content-Transfer-Encoding: base64
Content-Disposition: inline; filename=sample.gif
Content-ID: 12345@12345

R0lGODlhPAA8AJEAAP9nAf9mAP///wAAACH5BAAAAAAALAAAAAA8ADwAAAL/RI6py+0Po5w0iIuz
3rz7LyTgSJabaKYqh66u2r7yGM82i9w6fsCW1sMwVjXaLxMMNXzJUg55bCqZR6c0ea2SiqAYVis1
hrvPyxeoFafJU3MUvP5w5ZazG0313dv8vWnuEST4hpcC2HFmJxQXWGa1yJeVZ1jVIkk5Rgel6GeV
2YhGCDX5BycKicmImPY1lKpX6KZA5Lizc2jrgptLynuz6+vJ+hPW6riwiBw6LLspO6ukvLR8Qtz0
dG092rdabD04uJ3d2c0c7TreF07mfSeyvpf5mWze866NKq/6XB2Pn65PTb8266aB+wZtU71/CFEc
fFYkIkN/4irho9OOG0GLTJ3gNcoIkqO6ix8XDgQoch6/QiErumRnkmXKmQJlnpwpzZXCmzxRnTMm
UWUwYUN/1SpKSyhSjPuWMnX6AhjUakqnLquANavWrVwZFAAAOw==
------=_Part_6_23910357.1302148173940--

------=_Part_5_26680060.1302148173940
Content-Type: image/gif; name=sample.gif
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=sample.gif

R0lGODlhPAA8AJEAAP9nAf9mAP///wAAACH5BAAAAAAALAAAAAA8ADwAAAL/RI6py+0Po5w0iIuz
3rz7LyTgSJabaKYqh66u2r7yGM82i9w6fsCW1sMwVjXaLxMMNXzJUg55bCqZR6c0ea2SiqAYVis1
hrvPyxeoFafJU3MUvP5w5ZazG0313dv8vWnuEST4hpcC2HFmJxQXWGa1yJeVZ1jVIkk5Rgel6GeV
2YhGCDX5BycKicmImPY1lKpX6KZA5Lizc2jrgptLynuz6+vJ+hPW6riwiBw6LLspO6ukvLR8Qtz0
dG092rdabD04uJ3d2c0c7TreF07mfSeyvpf5mWze866NKq/6XB2Pn65PTb8266aB+wZtU71/CFEc
fFYkIkN/4irho9OOG0GLTJ3gNcoIkqO6ix8XDgQoch6/QiErumRnkmXKmQJlnpwpzZXCmzxRnTMm
UWUwYUN/1SpKSyhSjPuWMnX6AhjUakqnLquANavWrVwZFAAAOw==
------=_Part_5_26680060.1302148173940--


長くなったので、AUソフトバンクは・・・。