首先,根據 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
沒有留言:
張貼留言