有奖捉虫:办公协同&微信生态&物联网文档专题 HOT
文档中心 > 邮件推送 > SMTP 文档 > 发送带附件的邮件
通过 SMTP 的方式发送带附件的邮件的方法,即构建一封 MIME 格式的邮件内容。

邮件 mime 格式

了解更多协议相关,请参见 MIME 协议
说明
MIME 消息由消息头和消息体两大部分组成。分别称为 邮件头邮件体

邮件头

说明
每条信息称为一个域,由域名后加“: ”和信息内容构成,可以是一行,较长的也可以占用多行。
域的首行必须“顶头”写,即左边不能有空白字符(空格和制表符)。
续行则必须以空白字符打头,且一个空白字符不是信息本身固有的(解码时要过滤掉)。
邮件头中不允许出现空行。有一些邮件不能被邮件客户端软件识别,显示的是原始码,就是因为首行是空行。
例如:
内容
示例
Date
Mon, 29 Jun 2009 18:39:03 +0800
From
abc@123.com
To
abc1@123.com
BCC
abc3@123.com
Subject
test
Message-ID
123@123.com
Mime-Version
1.0
域名
含义
Bcc
暗送地址
Cc
抄送地址
Content-Transfer-Encoding
内容的传输编码方式
Content-Type
内容的类型
Date
日期和时间
Delivered-To
发送地址
From
发件人地址
Message-ID
消息 ID
MIME-Version
MIME 版本
Received
传输路径
Reply-To
回复地址
Return-Path
回复地址
Subject
主题
To
收件人地址

邮件体

域名
含义
Content-ID
段体的 ID
Content-Transfer-Encoding
段体的传输编码方式
Content-Location
段体的位置(路径)
Content-Base
段体的基位置
Content-Disposition
段体的安排方式
Content-Type
段体的类型
有些域除了值之外,还带有参数。值与参数、参数与参数之间以“;”分隔。参数名与参数值之间以“=”分隔。
邮件体包含邮件的内容,它的类型由邮件头的Content-Type域指出。
说明
常见的简单类型有:
text/plain(纯文本)
text/html(超文本)
multipart 类型,是 MIME 邮件的精髓。邮件体被分为多个段,每个段又包含段头和段体两部分,这两部分之间也以空行分隔。
常见的 multipart 类型有三种:
multipart/mixed
multipart/related
multipart/alternative 可从上述名称,得知这些类型各自的含义和用处。它们之间的层次关系可归纳为下图所示:

如果在邮件中要添加附件,必须定义 multipart/mixed 段;如果存在内嵌资源,至少要定义 multipart/related 段;如果纯文本与超文本共存,至少要定义 multipart/alternative 段。
说明
附件个数不超过10个,单个附件大小不超过4M,总附件大小不超过8M,具体可参见 数据结构

代码示例

package main
import (
"bytes"
"crypto/tls"
"encoding/base64"
"fmt"
"io/ioutil"
"log"
"mime"
"net"
"net/smtp"
"time"
)

// Test465Attachment for port 465
func Test465Attachment() error {
boundary := "GoBoundary"
host := "smtp.qcloudmail.com"
port := 465
email := "abc@cd.com"
password := "***"
toEmail := "test@test123.com"
header := make(map[string]string)
header["From"] = "test " + "<" + email + ">"
header["To"] = toEmail
header["Subject"] = "Test465Attachment"
header["Content-Type"] = "multipart/mixed;boundary=" + boundary
//该字段暂时没有用到 ,默认传1.0
header["Mime-Version"] = "1.0"
//该字段暂时没有用到
header["Date"] = time.Now().String()
bodyHtml := "<!DOCTYPE html>\\n<html>\\n<head>\\n<meta charset=\\"utf-8\\">\\n<title>hello world</title>\\n</head>\\n<body>\\n " +
"<h1>我的第一个标题</h1>\\n <p>我的第一个段落。</p>\\n</body>\\n</html>"
message := ""
for k, v := range header {
message += fmt.Sprintf("%s: %s\\r\\n", k, v)
}
buffer := bytes.NewBuffer(nil)
buffer.WriteString(message)
contentType := "Content-Type: text/html" + "; charset=UTF-8"
body := "\\r\\n--" + boundary + "\\r\\n"
body += "Content-Type:" + contentType + "\\r\\n"
body += "\\r\\n" + bodyHtml + "\\r\\n"
buffer.WriteString(body)

attachment := "\\r\\n--" + boundary + "\\r\\n"
attachment += "Content-Transfer-Encoding:base64\\r\\n"
attachment += "Content-Disposition:attachment\\r\\n"
attachment += "Content-Type:" + "application/octet-stream" + ";name=\\"" + mime.BEncoding.Encode("UTF-8",
"./go.mod") + "\\"\\r\\n"
buffer.WriteString(attachment)
writeFile(buffer, "./go.mod")
//多个附件往后拼接,最多不能超过10个附件,单个附件不能超过5M,所有附件累计不能超过8-9M,消息体过大会返回EOF
attachment1 := "\\r\\n--" + boundary + "\\r\\n"
attachment1 += "Content-Transfer-Encoding:base64\\r\\n"
attachment1 += "Content-Disposition:attachment\\r\\n"
attachment1 += "Content-Type:" + "application/octet-stream" + ";name=\\"" + mime.BEncoding.Encode("UTF-8",
"./bbbb.txt") + "\\"\\r\\n"
buffer.WriteString(attachment1)
writeFile(buffer, "./bbbb.txt")
defer func() {
if err := recover(); err != nil {
log.Fatalln(err)
}
}()

buffer.WriteString("\\r\\n--" + boundary + "--")
message += "\\r\\n" + body
auth := smtp.PlainAuth(
"",
email,
password,
host,
)
err := SendMailWithTLS(
fmt.Sprintf("%s:%d", host, port),
auth,
email,
[]string{toEmail},
buffer.Bytes(),
)
if err != nil {
fmt.Println("Send email error:", err)
} else {
fmt.Println("Send mail success!")
}
return err
}

// Dial return a smtp client
func Dial(addr string) (*smtp.Client, error) {
conn, err := tls.Dial("tcp", addr, nil)
if err != nil {
log.Println("tls.Dial Error:", err)
return nil, err
}

host, _, _ := net.SplitHostPort(addr)
return smtp.NewClient(conn, host)
}

// SendMailWithTLS send email with tls
func SendMailWithTLS(addr string, auth smtp.Auth, from string,
to []string, msg []byte) (err error) {
//create smtp client
c, err := Dial(addr)
if err != nil {
log.Println("Create smtp client error:", err)
return err
}
defer c.Close()
if auth != nil {
if ok, _ := c.Extension("AUTH"); ok {
if err = c.Auth(auth); err != nil {
log.Println("Error during AUTH", err)
return err
}
}
}
if err = c.Mail(from); err != nil {
return err
}
for _, addr := range to {
if err = c.Rcpt(addr); err != nil {
return err
}
}
w, err := c.Data()
if err != nil {
return err
}
_, err = w.Write(msg)
if err != nil {
return err
}
err = w.Close()
if err != nil {
return err
}
return c.Quit()
}

// writeFile read file to buffer
func writeFile(buffer *bytes.Buffer, fileName string) {
file, err := ioutil.ReadFile(fileName)
if err != nil {
panic(err.Error())
}
payload := make([]byte, base64.StdEncoding.EncodedLen(len(file)))
base64.StdEncoding.Encode(payload, file)
buffer.WriteString("\\r\\n")
for index, line := 0, len(payload); index < line; index++ {
buffer.WriteByte(payload[index])
if (index+1)%76 == 0 {
buffer.WriteString("\\r\\n")
}
}
}

func main() {
Test465Attachment()
}