本文介绍如何通过SMTP方式发送带附件的邮件。
通过 SMTP 的方式发送带附件的邮件的方法就是:构建一封 MIME 格式的邮件内容。
MIME 基础知识
MIME( Multipurpose Internet Mail Extensions) 多用途Internet邮件扩展。
RFC 地址:https://www.ietf.org/rfc/rfc2045.txt 。
说明
附件邮件总大小不超过15MB,一次最多不超过100个附件。
15MB是指smtp发信邮件实际总大小,由于base64编码邮件代码会膨胀1.5倍以上,总大小非客户侧看到的大小,附件限制建议按照8MB来准备。若需要发送大附件,建议内容里加超链接的方式发送。
代码示例(python 2.7)
# -*- coding:utf-8 -*-
import urllib, urllib2
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
from email.header import Header
# 发件人地址,通过控制台创建的发件人地址
username = 'xxx@xxx.com'
# 发件人密码,通过控制台创建的发件人密码
password = 'XXXXXXXX'
# 收件人地址列表,支持多个收件人,最多30个
rcptlist = ['to1@to.com', 'to2@to.com']
receivers = ','.join(rcptlist)
# 构建 multipart 的邮件消息
msg = MIMEMultipart('mixed')
msg['Subject'] = 'Test Email'
msg['From'] = username
msg['To'] = receivers
# 构建 multipart/alternative 的 text/plain 部分
alternative = MIMEMultipart('alternative')
textplain = MIMEText('纯文本部分', _subtype='plain', _charset='UTF-8')
alternative.attach(textplain)
# 构建 multipart/alternative 的 text/html 部分
texthtml = MIMEText('超文本部分', _subtype='html', _charset='UTF-8')
alternative.attach(texthtml)
# 将 alternative 加入 mixed 的内部
msg.attach(alternative)
# 附件类型
# xlsx 类型的附件
xlsxpart = MIMEApplication(open('测试文件1.xlsx', 'rb').read())
xlsxpart.add_header('Content-Disposition', 'attachment', filename=Header("测试文件1.xlsx","utf-8").encode())
msg.attach(xlsxpart)
# jpg 类型的附件
jpgpart = MIMEApplication(open('2.jpg', 'rb').read())
jpgpart.add_header('Content-Disposition', 'attachment', filename=Header("2.jpg","utf-8").encode())
msg.attach(jpgpart)
# mp3 类型的附件
mp3part = MIMEApplication(open('3.mp3', 'rb').read())
mp3part.add_header('Content-Disposition', 'attachment', filename=Header("3.mp3","utf-8").encode())
msg.attach(mp3part)
# 发送邮件
try:
client = smtplib.SMTP()
#python 2.7以上版本,若需要使用SSL,可以这样创建client
#client = smtplib.SMTP_SSL()
client.connect('smtpdm.aliyun.com')
client.login(username, password)
#发件人和认证地址必须一致
client.sendmail(username, rcptlist, msg.as_string())
client.quit()
print '邮件发送成功!'
except smtplib.SMTPRecipientsRefused:
print '邮件发送失败,收件人被拒绝'
except smtplib.SMTPAuthenticationError:
print '邮件发送失败,认证错误'
except smtplib.SMTPSenderRefused:
print '邮件发送失败,发件人被拒绝'
except smtplib.SMTPException,e:
print '邮件发送失败, ', e.message
代码示例(GO)
package main
import (
"bytes"
"encoding/base64"
"fmt"
"io/ioutil"
"log"
"mime"
"net/smtp"
"strings"
"time"
)
// define email interface, and implemented auth and send method
type Mail interface {
Auth()
Send(message Message) error
}
type SendMail struct {
user string
password string
host string
port string
auth smtp.Auth
}
type Attachment struct {
name string
contentType string
withFile bool
}
type Message struct {
from string
to []string
cc []string
bcc []string
subject string
body string
contentType string
attachment Attachment
}
func main() {
user := "XXX@XXXXX.top"
password := "TESXXXXXX"
host := "smtpdm.aliyun.com"
port := "80"
var mail Mail
mail = &SendMail{user: user, password: password, host: host, port: port}
message := Message{from: user,
to: []string{"XXXXX@qq.com", "XX@qq.com", "XXX@163.com"},
cc: []string{},
bcc: []string{},
subject: "HELLO WORLD",
body: "测试内容",
contentType: "text/plain;charset=utf-8",
// attachment: Attachment{
// name: "test.jpg",
// contentType: "image/jpg",
// withFile: true,
// },
attachment: Attachment{
name: "D:\\goProjects\\src\\测试pdf.pdf",
contentType: "application/octet-stream",
withFile: true,
},
}
err := mail.Send(message)
if err != nil {
fmt.Println("Send mail error!")
fmt.Println(err)
} else {
fmt.Println("Send mail success!")
}
}
func (mail *SendMail) Auth() {
// mail.auth = smtp.PlainAuth("", mail.user, mail.password, mail.host)
mail.auth = LoginAuth(mail.user, mail.password)
}
func (mail SendMail) Send(message Message) error {
mail.Auth()
buffer := bytes.NewBuffer(nil)
boundary := "GoBoundary"
Header := make(map[string]string)
Header["From"] = message.from
Header["To"] = strings.Join(message.to, ";")
Header["Cc"] = strings.Join(message.cc, ";")
Header["Bcc"] = strings.Join(message.bcc, ";")
Header["Subject"] = message.subject
Header["Content-Type"] = "multipart/mixed;boundary=" + boundary
Header["Mime-Version"] = "1.0"
Header["Date"] = time.Now().String()
mail.writeHeader(buffer, Header)
body := "\r\n--" + boundary + "\r\n"
body += "Content-Type:" + message.contentType + "\r\n"
body += "\r\n" + message.body + "\r\n"
buffer.WriteString(body)
if message.attachment.withFile {
attachment := "\r\n--" + boundary + "\r\n"
attachment += "Content-Transfer-Encoding:base64\r\n"
attachment += "Content-Disposition:attachment\r\n"
attachment += "Content-Type:" + message.attachment.contentType + ";name=\"" + mime.BEncoding.Encode("UTF-8", message.attachment.name) + "\"\r\n"
buffer.WriteString(attachment)
defer func() {
if err := recover(); err != nil {
log.Fatalln(err)
}
}()
mail.writeFile(buffer, message.attachment.name)
}
to_address := MergeSlice(message.to, message.cc)
to_address = MergeSlice(to_address, message.bcc)
buffer.WriteString("\r\n--" + boundary + "--")
err := smtp.SendMail(mail.host+":"+mail.port, mail.auth, message.from, to_address, buffer.Bytes())
return err
}
func MergeSlice(s1 []string, s2 []string) []string {
slice := make([]string, len(s1)+len(s2))
copy(slice, s1)
copy(slice[len(s1):], s2)
return slice
}
func (mail SendMail) writeHeader(buffer *bytes.Buffer, Header map[string]string) string {
header := ""
for key, value := range Header {
header += key + ":" + value + "\r\n"
}
header += "\r\n"
buffer.WriteString(header)
return header
}
// read and write the file to buffer
func (mail SendMail) 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")
}
}
}
type loginAuth struct {
username, password string
}
func LoginAuth(username, password string) smtp.Auth {
return &loginAuth{username, password}
}
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
// return "LOGIN", []byte{}, nil
return "LOGIN", []byte(a.username), nil
}
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if more {
switch string(fromServer) {
case "Username:":
return []byte(a.username), nil
case "Password:":
return []byte(a.password), nil
}
}
return nil, nil
}
其他示例代码参考: