
aws ses 的 sendemail api 不支持直接添加附件,必须改用 sendrawemail api 构造符合 mime 标准的原始邮件(raw message),手动编码附件内容(如 pdf)并嵌入多部分(multipart/mixed)结构中。
aws ses 的 sendemail api 不支持直接添加附件,必须改用 sendrawemail api 构造符合 mime 标准的原始邮件(raw message),手动编码附件内容(如 pdf)并嵌入多部分(multipart/mixed)结构中。
要通过 AWS SES 使用 Go SDK 发送带附件(例如 PDF 文件,以 []byte 形式存在)的邮件,核心原则是:放弃 SendEmail,改用 SendRawEmail。这是因为 SendEmail 仅支持纯文本或 HTML 正文,不提供附件机制;而 SendRawEmail 允许你传入完全自定义的、符合 RFC 5322 和 MIME 规范的原始字节流(RawMessage.Data),从而可灵活组织正文、HTML、纯文本及任意数量的 Base64 编码附件。
以下是一个生产就绪的完整示例(含错误处理与关键注释):
package main
import (
"bytes"
"encoding/base64"
"fmt"
"mime/multipart"
"net/textproto"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ses"
)
func sendEmailWithAttachment(
sesSvc *ses.SES,
from, to, subject string,
htmlBody []byte,
pdfData []byte,
pdfFilename string,
) error {
// 1. 创建内存缓冲区和 multipart writer
var buf bytes.Buffer
writer := multipart.NewWriter(&buf)
// 2. 设置根 boundary(自动由 writer 生成)
rootBoundary := writer.Boundary()
// 3. 写入邮件头(必需:From, To, Subject, MIME-Version, Content-Type)
headers := textproto.MIMEHeader{}
headers.Set("From", from)
headers.Set("To", to)
headers.Set("Subject", subject)
headers.Set("MIME-Version", "1.0")
headers.Set("Content-Type", fmt.Sprintf(`multipart/mixed; boundary="%s"`, rootBoundary))
// 4. 将头部写入缓冲区(注意结尾需两个 \r\n)
for key, values := range headers {
for _, value := range values {
fmt.Fprintf(&buf, "%s: %s\r\n", key, value)
}
}
fmt.Fprint(&buf, "\r\n") // 空行分隔 header 与 body
// 5. 创建 multipart/alternative 部分(用于兼容纯文本+HTML)
altWriter := multipart.NewWriter(&buf)
altBoundary := altWriter.Boundary()
fmt.Fprintf(&buf, "--%s\r\n", rootBoundary)
fmt.Fprintf(&buf, "Content-Type: multipart/alternative; boundary=\"%s\"\r\n\r\n", altBoundary)
// 5a. 添加纯文本部分(可选但推荐)
fmt.Fprintf(&buf, "--%s\r\n", altBoundary)
fmt.Fprint(&buf, "Content-Type: text/plain; charset=UTF-8\r\n\r\n")
fmt.Fprint(&buf, "This email requires HTML support.\r\n\r\n")
// 5b. 添加 HTML 部分
fmt.Fprintf(&buf, "--%s\r\n", altBoundary)
fmt.Fprint(&buf, "Content-Type: text/html; charset=UTF-8\r\n\r\n")
buf.Write(htmlBody)
fmt.Fprint(&buf, "\r\n\r\n")
// 5c. 结束 alternative 部分
fmt.Fprintf(&buf, "--%s--\r\n", altBoundary)
// 6. 添加附件(PDF)
fmt.Fprintf(&buf, "--%s\r\n", rootBoundary)
fmt.Fprintf(&buf, "Content-Type: application/pdf; name=\"%s\"\r\n", pdfFilename)
fmt.Fprint(&buf, "Content-Transfer-Encoding: base64\r\n")
fmt.Fprintf(&buf, "Content-Disposition: attachment; filename=\"%s\"\r\n\r\n", pdfFilename)
// Base64 编码并写入附件数据(每行最多 76 字符)
encoder := base64.NewEncoder(base64.StdEncoding, &buf)
encoder.Write(pdfData)
encoder.Close()
fmt.Fprint(&buf, "\r\n\r\n")
// 7. 结束 multipart/mixed
fmt.Fprintf(&buf, "--%s--\r\n", rootBoundary)
// 8. 调用 SendRawEmail
params := &ses.SendRawEmailInput{
RawMessage: &ses.RawMessage{
Data: buf.Bytes(),
},
Source: aws.String(from),
Destinations: []*string{aws.String(to)},
}
_, err := sesSvc.SendRawEmail(params)
return err
}
// 使用示例
func main() {
sess := session.Must(session.NewSessionWithOptions(session.Options{
SharedConfigState: session.SharedConfigState{
Config: aws.Config{Region: aws.String("us-east-1")},
},
}))
svc := ses.New(sess)
html := []byte("<h1>Hello!</h1><p>Your PDF is attached.</p>")
pdfBytes := []byte("%PDF-1.4...") // 替换为真实 PDF 字节
if err := sendEmailWithAttachment(
svc,
"sender@example.com",
"recipient@example.com",
"Invoice Attached",
html,
pdfBytes,
"invoice.pdf",
); err != nil {
panic(err)
}
}⚠️ 关键注意事项:
- MIME 结构必须严格合规:缺失空行、错位 boundary、编码错误均会导致 SES 拒绝(HTTP 400)或收件端解析失败;
-
附件名需 URL 安全:避免空格、中文等特殊字符,建议使用
path.Base()+url.PathEscape处理; - 大小限制:SES 单封 raw email 总大小上限为 40 MB(含所有附件及编码膨胀);
-
Base64 编码不可省略:二进制文件(如 PDF)必须 Base64 编码,并设置
Content-Transfer-Encoding: base64; - 测试先行:建议先用本地 SMTP 或 MailHog 捕获 raw message 进行结构验证,再对接 SES;
- 安全加固:生产环境务必对用户上传的附件进行病毒扫描、类型校验(非仅扩展名)及大小限制。
总结:发送带附件的 SES 邮件本质是构建标准 MIME 邮件,而非调用更高阶 API。掌握 multipart/mixed 与 multipart/alternative 嵌套逻辑、正确设置 headers 与 encoding,即可可靠支撑发票、报告、合同等业务场景。

















