How to make the attached pdf file be downloadable when sending email with go/smtp?

huangapple go评论130阅读模式
英文:

How to make the attached pdf file be downloadable when sending email with go/smtp?

问题

我尝试使用Go和smtp.office365.com发送包含PDF文件的电子邮件。

我找到了以下页面,并可以发送带有PDF文件的电子邮件。

https://zetcode.com/golang/email-smtp/

然而,如下附图所示,PDF文件被编码为base64。

enter image description here

是否有任何解决方案,使附加的PDF文件可以像普通邮件一样可下载(在gmail、outlook等中附加文件)?

以下是我使用的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用于smtp登录认证
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("未知的服务器响应")
		}
	}
	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("发送邮件失败")
		return
	}
	log.Fatalln("发送邮件成功")
}

func main() {
    server := "smtp.office365.com"
	port := 587
	sender := "我的邮箱"
	password := "我的密码"
	receiver := []string{"要发送的邮箱@gmail.com"}
	title := "测试\r\n"
	body := "测试\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文件,而不是base64字符串。

英文:

I try to send the email containing pdf file by using Go and smtp.office365.com.

I found the following page and can send the email with pdf file.

https://zetcode.com/golang/email-smtp/

However, the pdf file is encoded as base64 as shown in the attached image below.

enter image description here

Is there any solution to make the attached pdf file be downloadable just like ordinary mail(attaching file with gmail, outlook, etc.)?

Here is the Go code that I used.

package main
import (
&quot;bytes&quot;
&quot;encoding/base64&quot;
&quot;errors&quot;
&quot;fmt&quot;
&quot;io/ioutil&quot;
&quot;log&quot;
&quot;net/smtp&quot;
&quot;strconv&quot;
&quot;strings&quot;
)
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 &amp;loginAuth{username, password}
}
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
return &quot;LOGIN&quot;, []byte(a.username), nil
}
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if more {
switch string(fromServer) {
case &quot;Username:&quot;:
return []byte(a.username), nil
case &quot;Password:&quot;:
return []byte(a.password), nil
default:
return nil, errors.New(&quot;Unknown from server&quot;)
}
}
return nil, nil
}
func BuildMail(mail Mail, filename string) []byte {
var buf bytes.Buffer
buf.WriteString(fmt.Sprintf(&quot;From: %s\r\n&quot;, mail.Sender))
buf.WriteString(fmt.Sprintf(&quot;To: %s\r\n&quot;, strings.Join(mail.To, &quot;;&quot;)))
buf.WriteString(fmt.Sprintf(&quot;Subject: %s\r\n&quot;, mail.Subject))
boundary := &quot;my-boundary-779&quot;
buf.WriteString(&quot;MIME-Version: 1.0\r\n&quot;)
buf.WriteString(fmt.Sprintf(&quot;Content-Type: multipart/mixed; boundary=%s\n&quot;, boundary))
buf.WriteString(fmt.Sprintf(&quot;\r\n--%s\r\n&quot;, boundary))
buf.WriteString(&quot;Content-Type: text/plain; charset=\&quot;utf-8\&quot;\r\n&quot;)
buf.WriteString(fmt.Sprintf(&quot;\r\n%s&quot;, mail.Body))
buf.WriteString(fmt.Sprintf(&quot;\r\n--%s\r\n&quot;, boundary))
buf.WriteString(&quot;Content-Type: text/plain; charset=\&quot;utf-8\&quot;\r\n&quot;)
buf.WriteString(&quot;Content-Transfer-Encoding: base64\r\n&quot;)
buf.WriteString(&quot;Content-Disposition: attachment; filename=&quot; + filename + &quot;\r\n&quot;)
buf.WriteString(&quot;Content-ID: &lt;&quot; + filename + &quot;&gt;\r\n\r\n&quot;)
data := readFile(filename)
b := make([]byte, base64.StdEncoding.EncodedLen(len(data)))
base64.StdEncoding.Encode(b, data)
buf.Write(b)
buf.WriteString(fmt.Sprintf(&quot;\r\n--%s&quot;, boundary))
buf.WriteString(&quot;--&quot;)
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+&quot;:&quot;+strconv.Itoa(port), auth, sender, to, data)
if err != nil {
log.Fatalln(&quot;Error&quot;)
return
}
log.Fatalln(&quot;Success&quot;)
}
func main() {
server := &quot;smtp.office365.com&quot;
port := 587
sender := &quot;my email&quot;
password := &quot;my pwd&quot;
receiver := []string{&quot;EmailIWantToSend@gmail.com&quot;}
title := &quot;TEST\r\n&quot;
body := &quot;test\r\n&quot;
attach_filename := &quot;report.pdf&quot;
Mail_send(sender, password, server, port, receiver, title, body, attach_filename)
}

Here is the email that I received.

MIME-Version: 1.0
Content-Type: multipart/mixed; boundary=my-boundary-779
--my-boundary-779
Content-Type: text/plain; charset=&quot;utf-8&quot;
test
--my-boundary-779
Content-Type: text/plain; charset=&quot;utf-8&quot;
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=report.pdf
Content-ID: &lt;report.pdf&gt;
JVBERi0xLjMKMyAwIG9iago8PC9UeXBlIC9QYWdlCi9QYXJlbnQgMSAwIFIKL1Jlc291cmNlcyAyIDAgUgovQ29udGVudHMgNCAwIFI+PgplbmRvYmoKNCAwIG9iago8PC9GaWx0ZXIgL0ZsYXRlRGVjb2RlIC9MZW5ndGggMTcyMz4+CnN0cmVhbQp4AZyaS2/dRg+G9/4Vs/w
... // I omit since base64 string is too long.
AgbiAKMDAwMDAwMzc0NSAwMDAwMCBuIAowMDAwMDA0MDUzIDAwMDAwIG4gCjAwMDAwMDQxNjYgMDAwMDAgbiAKdHJhaWxlcgo8PAovU2l6ZSAxMQovUm9vdCAxMCAwIFIKL0luZm8gOSAwIFIKPj4Kc3RhcnR4cmVmCjQyNjQKJSVFT0YK
--my-boundary-779--

I want that the email receiver can download the attached pdf file directly, not the base64 string.

答案1

得分: 1

正如我怀疑的那样,你在消息头部使用了一个空行进行分隔。一个完整的消息由头字段和一个空行分隔的正文组成,如下所示:

From: Alice &lt;alice@example.org&gt;
To: Bob &lt;bob@example.com&gt;
Cc: Carol &lt;carol@example.com&gt;
Subject: A simple example message
Date: Fri, 07 Oct 2022 16:44:37 +0200
Message-ID: &lt;unique-identifier@example.org&gt;
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii

Hello Bob,

I think we should switch our roles.
How about you contact me from now on?

Best regards,
Alice

然而,你在主题的末尾加了一个换行符 (title := &quot;TEST\r\n&quot;),然后又写了一个换行符 (buf.WriteString(fmt.Sprintf(&quot;Subject: %s\r\n&quot;, mail.Subject)))。因此,MIME-Version: 1.0 被认为是消息正文的一部分,而不是头部。请再试一次,去掉 title 末尾的换行符。正如 Steffen Ullrich 指出的,当你发送 PDF 时,应将 Content-Type: text/plain; charset=&quot;utf-8&quot; 替换为 Content-Type: application/pdf(字符集也是不必要的)。

英文:

As suspected, you break your message headers with an empty line. A complete message consists of header fields and a body separated by an empty line, which looks as follows:

From: Alice &lt;alice@example.org&gt;
To: Bob &lt;bob@example.com&gt;
Cc: Carol &lt;carol@example.com&gt;
Subject: A simple example message
Date: Fri, 07 Oct 2022 16:44:37 +0200
Message-ID: &lt;unique-identifier@example.org&gt;
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii

Hello Bob,

I think we should switch our roles.
How about you contact me from now on?

Best regards,
Alice

You, however, end the subject with a newline (title := &quot;TEST\r\n&quot;) and then write another newline (buf.WriteString(fmt.Sprintf(&quot;Subject: %s\r\n&quot;, mail.Subject))). As a consequence, MIME-Version: 1.0 is considered to be part of the message body instead of the header. Try it again without the newline at the end of title. And as Steffen Ullrich noted, you should replace Content-Type: text/plain; charset=&quot;utf-8&quot; with Content-Type: application/pdf when you send a PDF. (The charset is also not necessary.)

huangapple
  • 本文由 发表于 2022年10月7日 17:16:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/73984960.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定