top / index / prev / next / target / source

2007-09-04 diary: [Java] JavaMail 調査 , JavaMailによるシンプルなメール送信サンプル

いがぴょんの日記 日記形式でつづる いがぴょんコラム ウェブページです。

old-v2

[Java] JavaMail 調査 , JavaMailによるシンプルなメール送信サンプル

JavaMailを調査しました。 シンプルなメール送信サンプルについて SSLあり、なしの両方を試作しました。 また添付ファイルについても調べました。

Java: JavaMail 調査

blanco FrameworkblancoMail に関して 上司の許可を得たので開発に着手することになりました。

blancoMail の開発に先立ち、まずは JavaMailの一次情報源は何かを調べました。すると 当然ですが Sunのサイトに行き着きます。

現時点では JavaMail API 1.4 リリースとの記載があり、最新版は 1.4とのことです。

また、2006.04.19時点で JavaMailはオープンソース化されているようです。 オープンソース化については 知りませんでした (あるいは忘れ去っていました)。 このような素晴らしいプロダクトがオープンソース化されることは喜ばしいことです。

そして、JavaMailについての 「公式な」 ドキュメント及びチュートリアルについては下記にあります。

JavaMail のインストール

JavaMailは ダウンロードして利用することになります。 (早期に Java SE に統合されることを希望しています)

現時点では 最新安定版は 以下のバージョンとなります。

JavaMailを利用するためには、JavaMail本体とは別に JavaBeans Activation Framework (JAF) と呼ばれるものが必要です。

ここで得られたファイルについて、classpathが通っているる場所に格納します。これで JavaMail API が利用可能になります。

シンプルな JavaMail メール送信サンプル

ここまで得られた情報源などをもとに、私なりに シンプルな JavaMail によるメール送信サンプルを作ってみました。

まず SSL無し版のメール送信サンプルを作りました。

SimpleSendMail.java

import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Properties;

import javax.mail.Address;
import javax.mail.AuthenticationFailedException;
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.InternetAddress;
import javax.mail.internet.MimeMessage;

/**
 * シンプルなメール送信サンプル。
 */
public class SimpleSendMail {
    // 日本語メールの場合には ISO-2022-JPがオススメ。
    // UTF-8だと受信時に文字化けしてしまうメーラが世の中には依然として存在しています。
    private static final String ENCODE = "ISO-2022-JP";

    public static void main(final String[] args) {
        System.out.println("メール送信開始");

        new SimpleSendMail().process();

        System.out.println("メール送信終了");
    }

    public void process() {
        final Properties props = new Properties();

        // 基本情報。ここでは niftyへの接続例を示します。
        props.setProperty("mail.smtp.host", "smtp.nifty.com");
        props.setProperty("mail.smtp.port", "25");

        // タイムアウト設定
        props.setProperty("mail.smtp.connectiontimeout", "60000");
        props.setProperty("mail.smtp.timeout", "60000");

        // 認証
        props.setProperty("mail.smtp.auth", "true");

        final Session session = Session.getInstance(props, new Authenticator() {
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication("NIFTYのID", "password");
            }
        });

        // デバッグを行います。標準出力にトレースが出ます。
        session.setDebug(true);

        // メッセージ内容の設定。
        final MimeMessage message = new MimeMessage(session);
        try {
            final Address addrFrom = new InternetAddress(
                    "送信者○○○@nifty.ne.jp", "送信者の表示名", ENCODE);
            message.setFrom(addrFrom);

            final Address addrTo = new InternetAddress("受信者○○○@gmail.com",
                    "受信者の表示名", ENCODE);
            message.addRecipient(Message.RecipientType.TO, addrTo);

            // メールのSubject
            message.setSubject("初めてのメール", ENCODE);

            // メール本文。setTextを用いると 自動的に[text/plain]となる。
            message.setText("こんにちは。\nごきげんよう。\nさようなら。", ENCODE);

            // 仮対策: 開始
            // setTextを呼び出した後に、ヘッダーを 7bitへと上書きします。
            // これは、一部のケータイメールが quoted-printable を処理できないことへの対策となります。
            message.setHeader("Content-Transfer-Encoding", "7bit");
            // 仮対策: 終了

            // その他の付加情報。
            message.addHeader("X-Mailer", "blancoMail 0.1");
            message.setSentDate(new Date());
        } catch (MessagingException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        // メール送信。
        try {
            Transport.send(message);
        } catch (AuthenticationFailedException e) {
            // 認証失敗は ここに入ります。
            System.out.println("指定のユーザ名・パスワードでの認証に失敗しました。"
                + e.toString());
        } catch (MessagingException e) {
            // smtpサーバへの接続失敗は ここに入ります。
            System.out.println("指定のsmtpサーバへの接続に失敗しました。" + e.toString());
            e.printStackTrace();
        }
    }
}

次に SSLを利用した版を作りました。(上記のソースコードに追記した形になっています。)

smtpサーバの接続情報によって SSL無し、有りの いずれのソースコードを利用するのかを選択してください。

SimpleSendMail.java

import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Properties;

import javax.mail.Address;
import javax.mail.AuthenticationFailedException;
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.InternetAddress;
import javax.mail.internet.MimeMessage;

/**
 * シンプルなメール送信サンプル。
 */
public class SimpleSendMail {
    // 日本語メールの場合には ISO-2022-JPがオススメ。
    // UTF-8だと受信時に文字化けしてしまうメーラが世の中には依然として存在しています。
    private static final String ENCODE = "ISO-2022-JP";

    public static void main(final String[] args) {
        System.out.println("メール送信開始");

        new SimpleSendMail().process();

        System.out.println("メール送信終了");
    }

    public void process() {
        final Properties props = new Properties();

        // 基本情報。ここでは gmailへの接続例を示します。
        props.setProperty("mail.smtp.host", "smtp.gmail.com");
        // SSL用にポート番号を変更。
        props.setProperty("mail.smtp.port", "465");

        // タイムアウト設定
        props.setProperty("mail.smtp.connectiontimeout", "60000");
        props.setProperty("mail.smtp.timeout", "60000");

        // 認証
        props.setProperty("mail.smtp.auth", "true");

        // SSL関連設定
        props.setProperty("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
        props.setProperty("mail.smtp.socketFactory.fallback", "false");
        props.setProperty("mail.smtp.socketFactory.port", "465");

        final Session session = Session.getInstance(props, new Authenticator() {
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication("送信者○○○@gmail.com", "password");
            }
        });

        // デバッグを行います。標準出力にトレースが出ます。
        session.setDebug(true);

        // メッセージ内容の設定。
        final MimeMessage message = new MimeMessage(session);
        try {
            final Address addrFrom = new InternetAddress(
                    "送信者○○○@gmail.com", "送信者の表示名", ENCODE);
            message.setFrom(addrFrom);

            final Address addrTo = new InternetAddress("受信者○○○@gmail.com",
                    "受信者の表示名", ENCODE);
            message.addRecipient(Message.RecipientType.TO, addrTo);

            // メールのSubject
            message.setSubject("初めてのメール", ENCODE);

            // メール本文。setTextを用いると 自動的に[text/plain]となる。
            message.setText("こんにちは。\nごきげんよう。\nさようなら。", ENCODE);

            // 仮対策: 開始
            // setTextを呼び出した後に、ヘッダーを 7bitへと上書きします。
            // これは、一部のケータイメールが quoted-printable を処理できないことへの対策となります。
            message.setHeader("Content-Transfer-Encoding", "7bit");
            // 仮対策: 終了

            // その他の付加情報。
            message.addHeader("X-Mailer", "blancoMail 0.1");
            message.setSentDate(new Date());
        } catch (MessagingException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        // メール送信。
        try {
            Transport.send(message);
        } catch (AuthenticationFailedException e) {
            // 認証失敗は ここに入ります。
            System.out.println("指定のユーザ名・パスワードでの認証に失敗しました。"
                + e.toString());
        } catch (MessagingException e) {
            // smtpサーバへの接続失敗は ここに入ります。
            System.out.println("指定のsmtpサーバへの接続に失敗しました。" + e.toString());
            e.printStackTrace();
        }
    }
}

当初想像していたよりは、すんなりと動作させることが出来ました。

ちなみに、session.setDebug(true) を指定して 実際のsmtpプロトコルのやりとりを観察することは とても重要です。 これは JavaMailを利用して実装する人の責務だと考えます。例えば、このサンプルソースの実行を試す際には、デバッグトレースを true に指定して、どのようなやりとりが smtpサーバとの間で行われているのかを観察すべきです。そして送信先のメールソフト側で実際に受信されたメールの内容をチェックすることも忘れてはなりません。メールソフトによっては 実際のメールのヘッダ部分を表示する機能を備えていますので、ヘッダ部分の表示をONに設定して意図したとおりのメール電文が受信側に届いているかどうかを確認しておきましょう。

関連するリソース

関連する日記

JavaMail メール送信の際の文字化け

2007.09.05追記 ケータイメールなどでは、Content-Transfer-Encoding が quoted-printable のものを適切に処理できないものが存在します。比較的新しい機種でも そのような実装のものがありました。(それが端末側の問題なのか、通信キャリア側のサーバの変換時の問題なのかについては、私は把握していません)ここまでで示したサンプルレベルでも、quoted-printable に起因するメールの文字化け対策についての考慮が必要なのだ、ということです (問題を見つけたので、サンプルソースには当面の対策を ほどこしました)。quoted-printable のレベルでダメとなると、難易度が高いですね…。とりあえず quoted-printable を処理できないメールソフトなんてものが、いまだに世の中に多く存在しているのだ、というのは 私にはとても意外でした。quoted-printable なんて かなり古い時期から存在していた指定内容だったように記憶しているのですが… (でもここは うろ覚えです)

添付ファイル付きの JavaMail メール送信サンプル

添付ファイル処理について 簡単に実装してみました。

こっちの方は プレーンテキストメールの送信に比べると 実装が やや厄介でした。API仕様にトラップがあるように感じました (苦笑)。まあ、一度作ってしまうことができたので、あとの応用は 比較的簡単そうです。 変更点のみ

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Properties;

import javax.activation.DataHandler;
import javax.mail.Address;
import javax.mail.AuthenticationFailedException;
import javax.mail.Authenticator;
import javax.mail.BodyPart;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
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;…中略…

            // 変更点: ここから
            // message.setTextの代わりに、添付ファイル処理を記述する

            final Multipart multipart = new MimeMultipart();

            // テキスト部分
            final MimeBodyPart textPart = new MimeBodyPart();
            textPart.setText("こんにちは。\nごきげんよう。\nさようなら。", ENCODE);

            // TODO Content-Transfer-Encoding の 7bit への上書きが必要かどうかは未調査。

            multipart.addBodyPart(textPart);

            // 添付ファイル部分
            final BodyPart attachmentPart = new MimeBodyPart();
            // 常にバイナリデータであるものとしてファイルを添付する。
            try {
                attachmentPart.setDataHandler(new DataHandler(
                        new ByteArrayDataSource(new BufferedInputStream(
                                new FileInputStream("添付ファイル名.txt")),
                                "application/octet-stream")));
            } catch (IOException e) {
                e.printStackTrace();
                return;
            }

            // 添付ファイル名をセット
            // MimeUtility.encodeText は 添付ファイル名にascii以外の文字が入る場合の対策。
            attachmentPart.setFileName(MimeUtility.encodeText("添付ファイル名.txt", ENCODE, null));
            multipart.addBodyPart(attachmentPart);

            message.setContent(multipart);

            // 変更点: ここまで

MimeUtility.encodeText の動きは とても良くできています。与えられた文字列について、エンコードが必要な場合のみエンコードし、エンコードが不要な場合にはエンコードしない、という仕様になっていました。こういう小さな気遣いが メール送信の実装には必要なのです!

ドメイン付きホスト名が要求される場合への対応

未確認情報

メールサーバによっては、ドメイン付きホスト名が要求される場合があります。JavaMail のデフォルトでは そのコンピュータのホスト名がセットされるのですが、これがドメイン付では無い場合に メールサーバへの HELLOコマンド時に拒否される場合があるようです。この場合には 自ホスト名をドメイン付きホスト名に変えてやれば良いです。プロパティにホスト名をセットすると、この問題を回避できるようです。(ただし、私は手元に再現環境が無いので確認を取っていません)

文字化け対策

ISO-2022-JP に関連して文字化けに遭遇する場合があります。Microsoft Outlookと同等の挙動をどうしても実現したい、そういった場合には、ISO-2022-JPを x-windows-iso2022jp として JavaVMごと動作させるという荒技があります。

この方法を推奨しているわけでは無いです。しかし、世の中には どうしても Outlookと挙動を合わせたいというニーズが意外に多いようなのです…。

世間のニュースから

登場キーワード


この日記について