GoでSMTPを使用してtextとhtmlをマルチパートにメール送信

既存のライブラリとかを色々調べた結果、うーんという感じだったので実装

テキストメールとHTMLメールの文字列を受け取って
・両方とも存在すればマルチパートで配信
・どちらか一方が空文字だったらマルチパートにはしないようにする。

※添付ファイルとかには未対応。

改めて、メールの作り方の勉強になりました。

参考（大部分を流用させて頂いてます。）
https://qiita.com/yamasaki-masahide/items/a9f8b43eeeaddbfb6b44


```
// SendMailAtSMTP SMTPを使用してメール送信
// textBody テキストメールの内容
// htmlBody HTMLメールの内容
func (serv *SendMailService) SendMailAtSMTP(subject, textBody, htmlBody string, toAddresses []string) error {

	c, err := smtp.Dial("localhost:25")
	if err != nil {
		return err
	}

	for _, email := range toAddresses {
		c.Mail(test@localhost) // 送り主
		c.Rcpt(email)       // 受信者

		wc, err := c.Data()
		if err != nil {
			return err
		}
		uuid := "乱数" // 何かしらで乱数生成

		var textBodyBuffer bytes.Buffer
		textBodyBuffer.WriteString(textBody)

		var htmlBodyBuffer bytes.Buffer
		htmlBodyBuffer.WriteString(htmlBody)

		var message bytes.Buffer
		boundary := ""

		// mail header
		message.WriteString("To: " + email + "\r\n")
		message.WriteString(encodeSubject(subject))
		message.WriteString("Mime-Version: 1.0\r\n")
		if textBody != "" && htmlBody != "" {
			message.WriteString("Content-Type: multipart/alternative; boundary=\"" + uuid + "\"\r\n")
			message.WriteString("\r\n")

			boundary = "--" + uuid + "\r\n"
		}

		// text/plain mail body
		if textBody != "" {
			message.WriteString(boundary)
			message.WriteString("Content-Type: text/plain; charset=\"UTF-8\"\r\n")
			message.WriteString("Content-Transfer-Encoding: base64\r\n")
			message.WriteString("\r\n")
			message.WriteString(add76crlf(base64.StdEncoding.EncodeToString(textBodyBuffer.Bytes())))
			message.WriteString("\r\n")
			message.WriteString("\r\n")
		}

		// text/html mail body
		if htmlBody != "" {
			message.WriteString(boundary)
			message.WriteString("Content-Type: text/html; charset=\"UTF-8\"\r\n")
			message.WriteString("Content-Transfer-Encoding: base64\r\n")
			message.WriteString("\r\n")
			message.WriteString(add76crlf(base64.StdEncoding.EncodeToString(htmlBodyBuffer.Bytes())))
			message.WriteString("\r\n")
			message.WriteString("\r\n")
		}

		// mail body end
		if textBody != "" && htmlBody != "" {
			message.WriteString("--" + uuid + "--\r\n")
		}

		if _, err = message.WriteTo(wc); err != nil {
			return err
		}

		wc.Close()
	}
	return c.Quit()
}

// 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()
}
```