首先,根據 RFC 會知道 Email 的內文格式如下:
Subject: This is a test mail! From: test@example.com Content-Type: multipart/mixed; boundary="qwertyuio" --qwertyuio This is the body of email. --qwertyuio--(換行要使用
"\r\n" ,不能是 "\n")
若要追加附件,則增加 mime body即可:
Subject: This is a test mail! From: test@example.com Content-Type: multipart/mixed; boundary="qwertyuio" --qwertyuio This is the body of email. --qwertyuio Content-Type: text/html; name="example.html" Content-Disposition: attachment; filename="example.html" <!DOCTYPE html> <html> <head><meta charset="UTF-8"/><title>Example</title></head> <body>This is an attachment!</body> </html> --qwertyuio Content-Type: text/plain; name="example.txt" Content-Disposition: attachment; filename="example.txt" An example text file --qwertyuio--
上面是寄送兩個附件檔(example.html 和 example.txt)的電子郵件。
如果要追加圖片之類的檔案,一樣做法,不過最好將圖片以 base64 加密。我試過不加密的話,寄出去會是混亂的圖。
知道如何寄後,接下來就能寫 GoLang code 了
寄信需要的是 smtp 這個 package,以 Gmail 為例,寄信的 code 如下:
package main import ( "net/smtp" ) // 以下 variable 可參考 Gmail 的 smtp 設定說明 var ( host = "smtp.gmail.com:587" username = "example@gmail.com" password = "Pass123" ) func main() { auth := smtp.PlainAuth(host, username, password, "smtp.gmail.com") to := []string{"recipient@example.net"} msg := []byte( "Subject: This is a test mail!\r\n" + "From: test@example.com\r\n" + `Content-Type: multipart/mixed; boundary="qwertyuio"` + "\r\n" + "\r\n" + "--qwertyuio\r\n" + "This is the body of email.\r\n" + "\r\n" + "--qwertyuio--\r\n", ) smtp.SendMail( host, auth, username, to, msg, ) }若要加入圖片,由於需要 base64 encode,需要再 import "encoding/base64" package,如下:
package main
import (
"encoding/base64"
"io/ioutil"
"log"
"net/smtp"
"strings"
)
var (
host = "smtp.gmail.com:587"
username = "example@gmail.com"
password = "Pass123"
)
func main() {
auth := smtp.PlainAuth(host, username, password, "smtp.gmail.com")
to := []string{"recipient@example.net"}
imageByte, err := ioutil.ReadFile("/tmp/sample.png")
if err != nil {
log.Fatal(err)
}
// 為方便閱讀,用 string array 存 mail body
// 之後再用 strings.join 合併
messages := []string{
"Subject: This is a test mail!",
"From: test@example.com",
"Content-Type: multipart/mixed; boundary=\"qwertyuio\"",
"",
"--qwertyuio",
"This is the body of email.",
"",
"--qwertyuio",
"Content-Type: image/png;",
"Content-Transfer-Encoding: base64",
"",
[]byte(
base64.StdEncoding.EncodeToString(
string(imageByte),
),
),
"--qwertyuio--",
}
err = smtp.SendMail(
host,
auth,
username,
to,
[]byte(strings.Join(messages, "\r\n")),
)
if err != nil {
log.Fatal(err)
}
}
但這樣子 mime body 多了的話就變得麻煩了,Go Lang 有提供 "mime/multipart" package 方便處理,概念上其實是一樣的:
package main
import (
"bytes"
"encoding/base64"
"io/ioutil"
"log"
"mime/multipart"
"net/smtp"
)
var (
host = "smtp.gmail.com:587"
username = "example@gmail.com"
password = "Pass123"
)
func main() {
auth := smtp.PlainAuth(host, username, password, "smtp.gmail.com")
to := []string{"recipient@example.net"}
boundary := "qwertyuiuhgfgh"
mailbody := &bytes.Buffer{}
mailbody.Write([]byte("Subject: This is a test mail!\r\n"))
mailbody.Write([]byte("From: test@example.com" + "\r\n"))
mailbody.Write([]byte(`Content-Type: multipart/mixed; boundary="` + boundary + `"` + "\r\n"))
w := multipart.NewWriter(mailbody)
w.SetBoundary(boundary)
// mail body
bw, err := w.CreatePart(map[string][]string{})
if err != nil {
log.Fatal(err)
}
bw.Write([]byte("This is the body of email."))
imageByte, err := ioutil.ReadFile("/tmp/sample.png")
if err != nil {
log.Fatal(err)
} else {
bw, _ = w.CreatePart(
map[string][]string{
"Content-Type": []string{`image/png`},
"Content-Disposition": []string{
`attachment; filename="sample.png"`,
},
"Content-Transfer-Encoding": []string{"base64"},
},
)
// 和先前的 base64.StdEncoding.EncodeToString 一樣
// 都是將讀取圖片得到的 byte array 以 base 64 加密
//
// 但 base64.StdEncoding.EncodeToString 回傳的是加密後的 String value
// 這次已經使用 io.Writer 了,就沒必要特地變成 String 再轉成 []byte
// 直接將加密後得到的 byte 輸入至 bw 中
encoder := base64.NewEncoder(base64.StdEncoding, bw)
encoder.Write(imageByte)
}
w.Close()
err = smtp.SendMail(
host,
auth,
username,
to,
mailbody.Bytes(),
)
if err != nil {
log.Fatal(err)
}
}
參考 RFC:
- Email 內文格式相關
- RFC 2822
- Boundary 的使用
- RFC 2046
沒有留言:
張貼留言