Golang Cafe #16 まとめ その2 smtpパッケージ

2014/02/09に開催された「Golang Cafe #16」についてのまとめsono2です。
前エントリーに続いてsmtpパッケージをまとめておきます。


+TakashiYokoyama氏が準備してくれたサンプルはこちらにあります。私の書いたサンプルはGitHubに置いてあります。

smtpパッケージ

smtpパッケージSMTPプロトコルにてメールを送信する機能を実装しているパッケージです。
メールを送信する方法は2つあります。
シンプルに1通のメールを送信する方法と、1セッションで複数のメールを送信する方法です。
また、SMTP認証方法はPLAINとCRAM-MD5をサポートしているようです。

シンプルに1通のメールを送信する
func main() {
    // 適宜変更してください
    smtpServer := "smtp.example.com:587"
    user := "ユーザ名"
    pass := "パスワード"
    from := mail.Address{"From表示名", "from@example.com"}
    to := mail.Address{"to表示名", "to@example.com"}
    cc := mail.Address{"cc表示名", "cc@example.com"}
    receivers := []string{to.Address, cc.Address}

    msg := "" +
        "From:" + from.String() + "\r\n" +
        "To:" + to.String() + "\r\n" +
        "Cc:" + cc.String() + "\r\n" +
        "Subject:SMTP Test\r\n" +
        "\r\n" +
        "This is a test mail."

    auth := smtp.PlainAuth("", user, pass, "smtp.example.com")
    //auth := smtp.CRAMMD5Auth(user, pass)

    err := smtp.SendMail(smtpServer, auth, from.Address, receivers, []byte(msg))

    if err != nil {
        fmt.Fprintf(os.Stderr, "Error: %v\n", err)
        os.Exit(1)
    }
}

特に難しい部分はありません。
smtp.SendMail関数でSMTPサーバー(ホスト:ポート)、認証方法、送信者アドレス、受信者一覧、本文を指定するだけです。
認証方法は上でも書いたようにPLAINCRAM-MD5を指定します。
受信者一覧にはTo、Cc、Bccすべての受信者のアドレスをしていします。
本文はSMTPプロトコルのDATAコマンドで送信する内容(詳細は省略)を指定します。
iso-2022-jpエンコードで送信する場合は、content-typeの指定や本文等のエンコードが必要になります。
前エントリーで使用したmime.multipartパッケージを使用すればmultipartなメールも送信できると思いますが、複雑になるのでサンプルは省略します。

1セッションで複数のメールを送信する

先の方法では1セッション(1回のサーバーへの接続と切断)で1通のメールしか送ることができません。複数件のメールを送信する場合は非効率です。
Go言語のsmtpパッケージはその点も考慮され、SMTPコマンドレベルの操作もできるようです。

func main() {
    smtpServer := "smtp.example.com:587"
    user := "ユーザ名"
    pass := "パスワード"
    from := mail.Address{"From表示名", "from@example.com"}
    to := []mail.Address{
        mail.Address{"to表示名", "to@example.com"},
        mail.Address{"cc表示名", "cc@example.com"}}

    auth := smtp.PlainAuth("", user, pass, "smtp.example.com")

    var client *smtp.Client
    var err error

    if client, err = smtp.Dial(smtpServer); err != nil {
        fmt.Fprintf(os.Stderr, "Error: %v\n", err)
        os.Exit(1)
    }

    defer client.Close()

    if err = client.Hello("localhost"); err != nil {
        fmt.Fprintf(os.Stderr, "Error: %v\n", err)
        os.Exit(1)
    }

    if err = client.Auth(auth); err != nil {
        fmt.Fprintf(os.Stderr, "Error: %v\n", err)
        os.Exit(1)
    }

    for _, addr := range to {
        if err = client.Reset(); err != nil {
            fmt.Fprintf(os.Stderr, "Error: %v\n", err)
            os.Exit(1)
        }

        if err = client.Mail(from.Address); err != nil {
            fmt.Fprintf(os.Stderr, "Error: %v\n", err)
            os.Exit(1)
        }

        if err = client.Rcpt(addr.Address); err != nil {
            fmt.Fprintf(os.Stderr, "Error: %v\n", err)
            os.Exit(1)
        }

        msg := "" +
            "From:" + from.String() + "\r\n" +
            "To:" + addr.String() + "\r\n" +
            "Subject:SMTP Test\r\n" +
            "\r\n" +
            "This is a test mail."

        var w io.WriteCloser

        if w, err = client.Data(); err != nil {
            fmt.Fprintf(os.Stderr, "Error: %v\n", err)
            os.Exit(1)
        }

        if _, err = w.Write([]byte(msg)); err != nil {
            fmt.Fprintf(os.Stderr, "Error: %v\n", err)
            os.Exit(1)
        }

        w.Close()
    }

    if err = client.Quit(); err != nil {
        fmt.Fprintf(os.Stderr, "Error: %v\n", err)
        os.Exit(1)
    }
}

smtp.Client型が用意されており、SMTPプロトコルで最低限必要なコマンドが実装されています。
SMTPプロトコルにしたがって処理を実装するだけです。メールの内容については、「シンプルに1通のメールを送信する」の場合と同じです。


「シンプルに1通のメールを送信する」の場合に使用するsmtp.SendMail関数のソースコードを確認したところ、関数内部では「1セッションで複数のメールを送信する」の場合と同じようにコマンドに対応したメソッドを実行しているだけでした。


Go言語では上記のいずれの場合にせよ簡単にSMTPでメールを送信することができますが、SMTPプロトコルに関する最低限の知識が必要なようです。

おまけ

次回のお題を決めようとパッケージ一覧をみていたところ、個人的にあるパッケージに衝撃を受けました。
textprotoパッケージです。
前回のGolang Cafe #15netパッケージを勉強したので、今回のmailパッケージに向けてPOP3でメールを受信するパッケージを作成しました。すべてスクラッチで書いたのですが、どうもtextprotoパッケージを使用すると非常に簡単に書けるようです(1行読みこんで、レスポンスコード確認して・・・とか)。なんだそんな便利なものがあったのか、と今思っているところです。textprotoパッケージを使用して書き直そうと思います。
ちなみにsmtpパッケージソースコードを確認したところ、もちろんですがtextprotoパッケージを使用して実装されていました。

追記

改めて調べてみたところtextprotoパッケージは数値のレスポンスコードしか想定していないようで、そのままでは使えなさそうです。

次回

次回はos.execパッケージあたりだったと思います。