go で utf8メールを送信

More than 5 years have passed since last update.

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より引用:

行の長さの制限

この標準では1行中の文字数に2つの制限がある。それぞれの行の文字は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-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=