一、邮件 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域指出。
注意:说明:
注意:常见的简单类型有:
·multipart 类型,是 MIME 邮件的精髓。邮件体被分为多个段,每个段又包含段头和段体两部分,这两部分之间也以空行分隔。
·常见的 multipart 类型有三种:
·multipart/mixed
·multipart/related
·multipart/alternative
可从上述名称,得知这些类型各自的含义和用处。它们之间的层次关系可归纳为下图所示:
如果在邮件中要添加附件,必须定义 multipart/mixed 段;如果存在内嵌资源,至少要定义 multipart/related 段;如果纯文本与超文本共存,至少要定义 multipart/alternative 段。
注意:说明:
注意:附件个数不超过10个,单个附件大小不超过4M,总附件大小不超过8M,具体可参见 数据结构。
四、Go 代码示例
package mainimport ( "bytes" "crypto/tls" "encoding/base64" "fmt" "io/ioutil" "log" "mime" "net" "net/smtp" "time")// Test465Attachment for port 465func 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 += contentType + "\\r\\n" body += "Content-Transfer-Encoding: base64\\r\\n" body += "\\r\\n" + base64.StdEncoding.EncodeToString([]byte(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 clientfunc 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 tlsfunc 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 bufferfunc 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()}
五、Python 代码示例
#!/usr/bin/env python3# -*- coding: utf-8 -*-import smtplibimport osfrom email.mime.multipart import MIMEMultipartfrom email.mime.text import MIMETextfrom email.mime.base import MIMEBasefrom email import encodersfrom email.utils import formatdatefrom email.header import Headerimport ssldef send_email_with_attachments(): # 邮件服务器配置 host = "smtp.qcloudmail.com" #不同站点请选择不同接入域名 port = 465 email = "abc@cd.com" #控制台注册的smtp用户名 password = "****" #控制台注册的smtp密码 to_email = "test@test123.com" # 创建MIME消息 msg = MIMEMultipart('mixed') msg['From'] = f"test <{email}>" msg['To'] = to_email msg['Subject'] = "Test465Attachment" msg['Date'] = formatdate(localtime=True) msg['Mime-Version'] = "1.0" # HTML正文内容 html_body = """ <!DOCTYPE html> <html> <head> <meta charset="utf-8"><title>hello world</title> </head> <body> <h1>我的第一个标题</h1> <p>我的第一个段落。</p> </body> </html> """ # 添加HTML正文部分 html_part = MIMEText(html_body, 'html', 'utf-8') msg.attach(html_part) # 添加附件 attachments = ["./中文名称.txt"] for attachment_path in attachments: if os.path.exists(attachment_path): try: # 读取附件文件 with open(attachment_path, 'rb') as file: attachment_data = file.read() # 创建MIMEBase对象 attachment_part = MIMEBase('application', 'octet-stream') attachment_part.set_payload(attachment_data) # 编码为base64 encoders.encode_base64(attachment_part) # 设置附件头信息 filename = os.path.basename(attachment_path) encoded_filename = Header(filename, 'utf-8').encode() attachment_part.add_header( 'Content-Disposition', f'attachment; filename="{encoded_filename}"' ) msg.attach(attachment_part) print(f"成功添加附件: {filename}") except Exception as e: print(f"添加附件 {attachment_path} 时出错: {e}") else: print(f"附件文件不存在: {attachment_path}") try: # 创建SSL上下文 context = ssl.create_default_context() # 连接SMTP服务器并发送邮件 with smtplib.SMTP_SSL(host, port, context=context) as server: server.login(email, password) server.send_message(msg) print("邮件发送成功!") return True except Exception as e: print(f"发送邮件时出错: {e}") return Falseif __name__ == "__main__": print("开始发送带附件的邮件…") send_email_with_attachments()
注意:如果在终端执行这段编码出现编码类报错,一般是终端的编码不是 UTF-8 导致。