如何用Go语言通过SMTP发送可直接下载的PDF附件邮件?
问题
使用Go语言结合smtp.office365.com发送带PDF附件的邮件,发送成功后收件人看到的PDF附件是base64字符串,无法直接下载。以下是使用的Go代码和收到的邮件原始内容:
发送代码
package main import ( "bytes" "encoding/base64" "errors" "fmt" "io/ioutil" "log" "net/smtp" "strconv" "strings" ) type loginAuth struct { username, password string } type Mail struct { Sender string To []string Subject string Body string } // LoginAuth is used for smtp login auth func LoginAuth(username, password string) smtp.Auth { return &loginAuth{username, password} } func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) { 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 default: return nil, errors.New("Unknown from server") } } return nil, nil } func BuildMail(mail Mail, filename string) []byte { var buf bytes.Buffer buf.WriteString(fmt.Sprintf("From: %s\r\n", mail.Sender)) buf.WriteString(fmt.Sprintf("To: %s\r\n", strings.Join(mail.To, ";"))) buf.WriteString(fmt.Sprintf("Subject: %s\r\n", mail.Subject)) boundary := "my-boundary-779" buf.WriteString("MIME-Version: 1.0\r\n") buf.WriteString(fmt.Sprintf("Content-Type: multipart/mixed; boundary=%s\n", boundary)) buf.WriteString(fmt.Sprintf("\r\n--%s\r\n", boundary)) buf.WriteString("Content-Type: text/plain; charset=\"utf-8\"\r\n") buf.WriteString(fmt.Sprintf("\r\n%s", mail.Body)) buf.WriteString(fmt.Sprintf("\r\n--%s\r\n", boundary)) buf.WriteString("Content-Type: text/plain; charset=\"utf-8\"\r\n") buf.WriteString("Content-Transfer-Encoding: base64\r\n") buf.WriteString("Content-Disposition: attachment; filename=" + filename + "\r\n") buf.WriteString("Content-ID: <" + filename + ">\r\n\r\n") data := readFile(filename) b := make([]byte, base64.StdEncoding.EncodedLen(len(data))) base64.StdEncoding.Encode(b, data) buf.Write(b) buf.WriteString(fmt.Sprintf("\r\n--%s", boundary)) buf.WriteString("--") return buf.Bytes() } func readFile(fileName string) []byte { data, err := ioutil.ReadFile(fileName) if err != nil { log.Fatal(err) } return data } func Mail_send(sender string, sender_pwd string, server string, port int, to []string, headerSubject string, body string, filename string) { auth := LoginAuth(sender, sender_pwd) request := Mail{ Sender: sender, To: to, Subject: headerSubject, Body: body, } data := BuildMail(request, filename) err := smtp.SendMail(server+":"+strconv.Itoa(port), auth, sender, to, data) if err != nil { log.Fatalln("Error") return } log.Fatalln("Success") } func main() { server := "smtp.office365.com" port := 587 sender := "my email" password := "my pwd" receiver := []string{"EmailIWantToSend@gmail.com"} title := "TEST\r\n" body := "test\r\n" attach_filename := "report.pdf" Mail_send(sender, password, server, port, receiver, title, body, attach_filename) }
收到的邮件原始内容
MIME-Version: 1.0 Content-Type: multipart/mixed; boundary=my-boundary-779 --my-boundary-779 Content-Type: text/plain; charset="utf-8" test --my-boundary-779 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename=report.pdf Content-ID: <report.pdf> JVBERi0xLjMKMyAwIG9iago8PC9UeXBlIC9QYWdlCi9QYXJlbnQgMSAwIFIKL1Jlc291cmNlcyAyIDAgUgovQ29udGVudHMgNCAwIFI+PgplbmRvYmoKNCAwIG9iago8PC9GaWx0ZXIgL0ZsYXRlRGVjb2RlIC9MZW5ndGggMTcyMz4+CnN0cmVhbQp4AZyaS2/dRg+G9/4Vs/w ... // 因base64字符串过长,此处省略 AgbiAKMDAwMDAwMzc0NSAwMDAwMCBuIAowMDAwMDA0MDUzIDAwMDAwIG4gCjAwMDAwMDQxNjYgMDAwMDAgbiAKdHJhaWxlcgo8PAovU2l6ZSAxMQovUm9vdCAxMCAwIFIKL0luZm8gOSAwIFIKPj4Kc3RhcnR4cmVmCjQyNjQKJSVFT0YK --my-boundary-779--
解决方案
问题出在两个核心点,修改后即可让收件人直接下载PDF附件:
修正附件的Content-Type
代码中把PDF附件的Content-Type设为text/plain; charset="utf-8",这会让邮件客户端将附件识别为纯文本,直接显示base64内容。需改为PDF对应的MIME类型:application/pdf。对base64编码内容做换行处理
MIME协议要求base64编码内容每行不超过76字符,直接输出长串base64会导致客户端解析异常。可使用base64.NewEncoder自动处理换行。额外格式优化
- Subject中不要带
\r\n,避免破坏邮件头部格式,将title := "TEST\r\n"改为title := "TEST"。 - 规范边界分隔符写法,确保结尾的
--boundary--前有正确换行。
- Subject中不要带
以下是修改后的关键代码片段:
func BuildMail(mail Mail, filename string) []byte { var buf bytes.Buffer buf.WriteString(fmt.Sprintf("From: %s\r\n", mail.Sender)) buf.WriteString(fmt.Sprintf("To: %s\r\n", strings.Join(mail.To, ";"))) buf.WriteString(fmt.Sprintf("Subject: %s\r\n", mail.Subject)) boundary := "my-boundary-779" buf.WriteString("MIME-Version: 1.0\r\n") buf.WriteString(fmt.Sprintf("Content-Type: multipart/mixed; boundary=%s\r\n", boundary)) buf.WriteString(fmt.Sprintf("\r\n--%s\r\n", boundary)) buf.WriteString("Content-Type: text/plain; charset=\"utf-8\"\r\n") buf.WriteString("\r\n") buf.WriteString(mail.Body) buf.WriteString(fmt.Sprintf("\r\n--%s\r\n", boundary)) buf.WriteString("Content-Type: application/pdf\r\n") buf.WriteString("Content-Transfer-Encoding: base64\r\n") buf.WriteString(fmt.Sprintf("Content-Disposition: attachment; filename=\"%s\"\r\n", filename)) buf.WriteString("\r\n") data := readFile(filename) encoder := base64.NewEncoder(base64.StdEncoding, &buf) encoder.Write(data) encoder.Close() buf.WriteString(fmt.Sprintf("\r\n--%s--\r\n", boundary)) return buf.Bytes() }
修改后重新发送,收件人即可看到可直接下载的PDF附件。
内容的提问来源于stack exchange,提问作者bakgu
相关产品推荐
相关产品推荐

