在 Golang 中附加文件并通过 SMTP 发送时没有正文部分的电子邮件

Posted

技术标签:

【中文标题】在 Golang 中附加文件并通过 SMTP 发送时没有正文部分的电子邮件【英文标题】:Email message with no body part when attaching a file and sending via SMTP in Golang 【发布时间】:2019-10-27 04:02:17 【问题描述】:

我正在尝试在 Go (Golang) 中发送包含电子邮件正文和文件附件(CSV 文件)的电子邮件。

我遵循多部分消息的mime 标准,但是我不太熟悉遵循该标准的消息的结构。我隐约遵循一位同事的 Python 代码 sn-p 作为使用 Python 库 email 的指南(我认为这是来自标准库),例如MIMETextMIMEMultipart

执行以下 Go 代码时未显示电子邮件正文

有什么问题? 如何发送包含该文件附件和电子邮件正文的电子邮件?

此函数应返回一个字节切片,用作从 Go 标准库调用 smtp.SendMail 的参数。请参阅下面的 cmets,解释收到的电子邮件消息(THIS DOES NOT SHOW UP [...]THIS ALSO DOES NOT SHOW UP [...])发生了什么。

func msgWithAttachment(subject, filePath string) ([]byte, error) 
    // this is the separator used for the various parts of the MIME message structure
    // identified as "boundary"
    bPlaceholder := "our-custom-separator"

    // the message setup of the common/standard initial part
    mime := bytes.NewBuffer(nil)
    mime.WriteString(fmt.Sprintf("Subject: %s\r\nMIME-Version: 1.0\r\n", subject))
    mime.WriteString(fmt.Sprintf("Content-Type: multipart/mixed; boundary=%s\r\n", bPlaceholder))

    // THIS DOES NOT SHOW UP AS THE BODY OF THE EMAIL...
    // mime.WriteString("\r\n")
    // mime.WriteString(fmt.Sprintf("--%s\r\n", bPlaceholder))
    // mime.WriteString("This should be the email message body (v1)...")
    // mime.WriteString("\r\n")

    // THIS ALSO DOES NOT SHOW UP AS THE BODY OF THE EMAIL...
    // BUT IS NEEDED OTHERWISE THE EMAIL MESSAGE SEEMS TO CONTAIN AS ATTACHMENT THE EMAIL MESSAGE ITSELF
    // (CONTAINING ITSELF THE REAL ATTACHMENT)
    mime.WriteString(fmt.Sprintf("--%s\r\n", bPlaceholder))
    mime.WriteString("Content-Type: text/plain; charset=utf-8\r\n")
    mime.WriteString("This should be the email message body (v2)...")

    // attach a file from the filesystem
    _, msgFilename := filepath.Split(filePath)
    mime.WriteString(fmt.Sprintf("\n--%s\r\n", bPlaceholder))
    mime.WriteString("Content-Type: application/octet-stream\r\n")
    mime.WriteString("Content-Description: " + msgFilename + "\r\n")
    mime.WriteString("Content-Transfer-Encoding: base64\r\n")
    mime.WriteString("Content-Disposition: attachment; filename=\"" + msgFilename + "\"\r\n\r\n")
    fileContent, err := ioutil.ReadFile(filePath) // read and encode the content of the file
    if err != nil 
        return nil, err
    
    b := make([]byte, base64.StdEncoding.EncodedLen(len(fileContent)))
    base64.StdEncoding.Encode(b, fileContent)
    mime.Write(b)

    // footer of the email message
    mime.WriteString("\r\n--" + bPlaceholder + "--\r\n\r\n")

    return mime.Bytes(), nil

【问题讨论】:

这似乎是不必要的手动 - 你看过standard library's SMTP package 还是流行的第三方gomail package? 我正在使用 Go 标准库中的 net/smtp,这些文档中指出“net/smtp 包是低级机制,不支持 DKIM 签名、MIME 附件(请参阅 mime/多部分包)或其他邮件功能”。另外:我试图避免使用 3rd 方库来保持最小化并且不必处理依赖项管理。该库当前未维护,此分支已维护:github.com/go-mail/mail 【参考方案1】:

巧合的是,前几天我也遇到了类似的问题。我需要在正文内容类型和正文本身的开头之间留一个空行。以下是这部分代码的更新行:

    mime.WriteString("Content-Type: text/plain; charset=utf-8\r\n")
    mime.WriteString("\r\nThis should be the email message body (v2)...")

为了清楚起见,这个换行符 (\r\n) 不必完全在此处,它可以附加到上面的 content-type 行。它只需要在 content-type 和 body 的开头之间看到一个空行。

我假设附件的附加没有问题,对吗?我的假设是这是因为在添加附件数据之前,您在 content-disposition 行的末尾有双换行符。

【讨论】:

【参考方案2】:

阅读 RFC 规范对我有帮助:https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html

请注意,封装边界必须出现在一行的开头,即在 CRLF 之后,并且该初始 CRLF 被认为是封装边界的一部分,而不是前面部分的一部分。边界必须紧跟另一个 CRLF 和下一部分的标头字段,或者是两个 CRLF,在这种情况下,下一部分没有标头字段(因此假定为 Content-Type text/普通)。

【讨论】:

以上是关于在 Golang 中附加文件并通过 SMTP 发送时没有正文部分的电子邮件的主要内容,如果未能解决你的问题,请参考以下文章

通过 smtp 发送电子邮件并更改发件人姓名

如何在 vc 6.0 中使用 smtp 附加 txt 文件

golang golang发送邮件网/ smtp SMTP

java在linux下调用smtp协议发送邮件

GoLang邮件发送Demo(继上篇msmtp)

将数组附加到 FormData 并通过 AJAX 发送