You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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附件:

  1. 修正附件的Content-Type
    代码中把PDF附件的Content-Type设为text/plain; charset="utf-8",这会让邮件客户端将附件识别为纯文本,直接显示base64内容。需改为PDF对应的MIME类型:application/pdf。

  2. 对base64编码内容做换行处理
    MIME协议要求base64编码内容每行不超过76字符,直接输出长串base64会导致客户端解析异常。可使用base64.NewEncoder自动处理换行。

  3. 额外格式优化

    • Subject中不要带\r\n,避免破坏邮件头部格式,将title := "TEST\r\n"改为title := "TEST"。
    • 规范边界分隔符写法,确保结尾的--boundary--前有正确换行。

以下是修改后的关键代码片段:

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 16:10:29