go で utf8メールを送信


asciiでの平文メールはnet/smtpで簡単に送信できるようですが、
http://kwmt27.net/index.php/2013/09/26/how-to-send-mail-with-golang/

utf8になると本文とsubjectのエンコード処理の面倒な部分を
やってくれるパッケージを探したんだけど見つからなかった。

本文はbase64でエンコードすれば良いんだけど、
subjectが若干面倒
http://d.hatena.ne.jp/y_fudi/20090501/1241173690

>で、メールに関するお約束のRFC2822にこんな規定がある。ヘッダーに限定されているわけではないが、メールの1行に並べていい文字数には制限があるようだ。
http://www.puni.net/~mimori/rfc/rfc2822.txtより引用：
行の長さの制限
この標準では１行中の文字数に２つの制限がある。それぞれの行の文字はCRLFを除いて、決して998文字以下でなければならず(MUST)、78文字以下であるべきである(SHOULD)。
そんなわけで、MIMEエンコードされたSubjectがずらっと1行に並んでしまうと、うっかり998文字を超えてしまうおそれがあるわけで、なんとか78文字くらいで改行するのがよろしいということだろう。
ちなみに、上で使ったのと同じ文字列をBecky!2.50.07の件名の欄に突っ込んで、メールを送ってみたら、やっぱり適度に改行されていた。
>>Subject: =?ISO-2022-JP?B?GyRCJDMkbCRPJUYlOSVIJWEhPCVrJE4lNRsoQg==?=
 =?ISO-2022-JP?B?GyRCJVYlOCUnJS8lSCQzJGwkTyVGJTklSBsoQg==?=
 =?ISO-2022-JP?B?GyRCJWEhPCVrJE4lNSVWJTglJyUvJUgkMxsoQg==?=
 =?ISO-2022-JP?B?GyRCJGwkTyVGJTklSCVhITwlayROJTUlVhsoQg==?=
 =?ISO-2022-JP?B?GyRCJTglJyUvJUgkMyRsJE8lRiU5JUglYRsoQg==?=
 =?ISO-2022-JP?B?GyRCITwlayROJTUlViU4JSclLyVIJDMkbBsoQg==?=
 =?ISO-2022-JP?B?GyRCJE8lRiU5JUglYSE8JWskTiU1JVYlOBsoQg==?=
 =?ISO-2022-JP?B?GyRCJSclLyVIGyhC?=

76文字以内(改行含めず)で収まるように適度に改行してmimeエンコードした上で先頭にスペースを入れるのがよさそう

サンプルを書いた。

```go:go-utf8-sendmail-example.go
package main

import (
    "bytes"
    "encoding/base64"
    "log"
    "net/mail"
    "net/smtp"
    "strings"
)

// 76バイト毎にCRLFを挿入する
func add76crlf(msg string) string {
    var buffer bytes.Buffer
    for k, c := range strings.Split(msg, "") {
        buffer.WriteString(c)
        if k%76 == 75 {
            buffer.WriteString("\r\n")
        }
    }
    return buffer.String()
}

// UTF8文字列を指定文字数で分割
func utf8Split(utf8string string, length int) []string {
    resultString := []string{}
    var buffer bytes.Buffer
    for k, c := range strings.Split(utf8string, "") {
        buffer.WriteString(c)
        if k%length == length-1 {
            resultString = append(resultString, buffer.String())
            buffer.Reset()
        }
    }
    if buffer.Len() > 0 {
        resultString = append(resultString, buffer.String())
    }
    return resultString
}

// サブジェクトをMIMEエンコードする
func encodeSubject(subject string) string {
    var buffer bytes.Buffer
    buffer.WriteString("Subject:")
    for _, line := range utf8Split(subject, 13) {
        buffer.WriteString(" =?utf-8?B?")
        buffer.WriteString(base64.StdEncoding.EncodeToString([]byte(line)))
        buffer.WriteString("?=\r\n")
    }
    return buffer.String()
}

func main() {


    from := mail.Address{"ふろむ", "from@hoge.fuga"}
    to := mail.Address{"とぅー", "to@fuga.hoge"}
    title := "長いサブジェクト長いサブジェクト長いサブジェクト長いサブジェクト長いサブジェクト長いサブジェクト長いサブジェクト"

    var body bytes.Buffer
    body.WriteString("本文本文本文本文本文本文本文本文本文本文本文本文本文本文本文本文本文本文\n")
    body.WriteString("本文本文本文本文本文本文本文本文本文本文本文本文本文本文本文本文本文本文\n")

    var header bytes.Buffer
    header.WriteString("From: " + from.String() + "\r\n")
    header.WriteString("To: " + to.String() + "\r\n")
    header.WriteString(encodeSubject(title))
    header.WriteString("MIME-Version: 1.0\r\n")
    header.WriteString("Content-Type: text/plain; charset=\"utf-8\"\r\n")
    header.WriteString("Content-Transfer-Encoding: base64\r\n")

    var message bytes.Buffer
    message = header
    message.WriteString("\r\n")
    message.WriteString(add76crlf(base64.StdEncoding.EncodeToString(body.Bytes())))

    err := smtp.SendMail(
        "localhost:25",
        nil,
        from.Address,
        []string{to.Address},
        []byte(message.String()),
    )
    if err != nil {
        log.Fatal(err)
    }
}

```

これで送信するとこんな感じになった

```
Subject: =?utf-8?B?6ZW344GE44K144OW44K444Kn44Kv44OI6ZW344GE44K144OW44K4?=
 =?utf-8?B?44Kn44Kv44OI6ZW344GE44K144OW44K444Kn44Kv44OI6ZW344GE?=
 =?utf-8?B?44K144OW44K444Kn44Kv44OI6ZW344GE44K144OW44K444Kn44Kv?=
 =?utf-8?B?44OI6ZW344GE44K144OW44K444Kn44Kv44OI6ZW344GE44K144OW?=
 =?utf-8?B?44K444Kn44Kv44OI?=
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: base64
Message-Id: <20140222115231.8DECE260593@st>
Date: Sat, 22 Feb 2014 20:52:31 +0900 (JST)

5pys5paH5pys5paH5pys5paH5pys5paH5pys5paH5pys5paH5pys5paH5pys5paH5pys5paH5pys
5paH5pys5paH5pys5paH5pys5paH5pys5paH5pys5paH5pys5paH5pys5paH5pys5paHCuacrOaW
h+acrOaWh+acrOaWh+acrOaWh+acrOaWh+acrOaWh+acrOaWh+acrOaWh+acrOaWh+acrOaWh+ac
rOaWh+acrOaWh+acrOaWh+acrOaWh+acrOaWh+acrOaWh+acrOaWh+acrOaWhwo=
```