
aws ses 的 sendemail api 不支持直接添加附件,必须改用 sendrawemail api 构造符合 mime 标准的原始邮件(raw message),才能嵌入 pdf 等二进制附件。
aws ses 的 sendemail api 不支持直接添加附件,必须改用 sendrawemail api 构造符合 mime 标准的原始邮件(raw message),才能嵌入 pdf 等二进制附件。
要通过 AWS SES 使用 Go SDK 发送带附件(如 PDF)的邮件,关键在于放弃 SendEmail,转而使用 SendRawEmail —— 因为后者允许你完全控制邮件的 MIME 结构,从而支持多部分(multipart/mixed)、Base64 编码附件、内联资源及自定义头信息。
✅ 正确做法:构造标准 MIME 原始邮件
SendRawEmailInput.RawMessage.Data 字段需传入一个完整的、符合 RFC 5322 和 MIME 规范的原始邮件字节流。它通常包含:
-
From,To,Subject,Date等标准头字段; -
MIME-Version: 1.0; -
Content-Type: multipart/mixed; boundary="..."; - 多个 MIME 部分:纯文本/HTML 正文 + 经 Base64 编码的附件(如 PDF)。
以下是一个完整、可运行的 Go 示例(使用 golang.org/x/net/html 非必需,但 mime/multipart 是核心):
package main
import (
"bytes"
"encoding/base64"
"fmt"
"io"
"mime/multipart"
"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 string,
pdfData []byte, // 例如:os.ReadFile("report.pdf")
filename string, // 如 "report.pdf"
) error {
// 1. 创建内存缓冲区和 multipart writer
var buf bytes.Buffer
writer := multipart.NewWriter(&buf)
// 2. 设置随机 boundary(writer 自动处理)
boundary := writer.Boundary()
// 3. 添加 HTML 正文部分
part, _ := writer.CreatePart(map[string][]string{
"Content-Type": {"text/html; charset=utf-8"},
})
io.WriteString(part, htmlBody)
// 4. 添加附件部分
attachmentPart, _ := writer.CreatePart(map[string][]string{
"Content-Type": {fmt.Sprintf("application/pdf; name=\"%s\"", filename)},
"Content-Disposition": {fmt.Sprintf("attachment; filename=\"%s\"", filename)},
"Content-Transfer-Encoding": {"base64"},
})
encoder := base64.NewEncoder(base64.StdEncoding, attachmentPart)
encoder.Write(pdfData)
encoder.Close()
// 5. 关闭 multipart writer(写入 final boundary)
writer.Close()
// 6. 构建原始邮件数据:含必要邮件头
rawEmail := fmt.Sprintf(
"From: %s\r\n"+
"To: %s\r\n"+
"Subject: %s\r\n"+
"MIME-Version: 1.0\r\n"+
"Content-Type: multipart/mixed; boundary=\"%s\"\r\n\r\n",
from, to, subject, boundary,
) + buf.String()
// 7. 调用 SendRawEmail
input := &ses.SendRawEmailInput{
RawMessage: &ses.RawMessage{
Data: []byte(rawEmail),
},
Source: aws.String(from),
Destinations: []*string{
aws.String(to),
},
}
_, err := sesSvc.SendRawEmail(input)
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)
err := sendEmailWithAttachment(
svc,
"sender@example.com",
"recipient@example.com",
"Monthly Report with PDF",
"<h1>Hello!</h1><p>Please find the attached report.</p>",
[]byte("%PDF-1.4..."), // 替换为真实 PDF 字节
"monthly-report.pdf",
)
if err != nil {
panic(err)
}
fmt.Println("Email sent successfully with attachment.")
}
⚠️ 注意事项与最佳实践
-
编码严格性:每行结尾必须是
\r\n(CRLF),不可用\n;空行分隔邮件头与正文。 -
附件命名安全:确保
filename经过清理(避免路径遍历或特殊字符),建议使用path.Base()+ 白名单校验。 - 大小限制:SES 单封原始邮件总大小上限为 40 MB(含所有附件和正文),注意 Base64 编码会使附件体积膨胀约 33%。
-
身份验证与权限:确保你的 IAM 用户/角色拥有
ses:SendRawEmail权限,且发信邮箱或域名已在 SES 中完成验证(生产环境需申请发送配额)。 -
调试技巧:先将
rawEmail字符串打印出来,用在线 MIME 解析器(如 https://www.php.cn/link/e7b51e38cc1234fff284d2dbaa301e3b)验证结构是否合法。 -
替代方案:若频繁处理复杂邮件,推荐封装复用的
MIMEBuilder工具类,或采用成熟库如gomail(需自行集成 SES 传输层)。
综上,SES 发送附件的本质是“手写 MIME”,虽略繁琐,但赋予了最大灵活性。掌握 SendRawEmail + multipart 模式,即可可靠支撑报表投递、合同签署、凭证下发等典型业务场景。










