英文:
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。
是否有任何解决方案,使附加的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.
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 (
"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)
}
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="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
... // 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 <alice@example.org>
To: Bob <bob@example.com>
Cc: Carol <carol@example.com>
Subject: A simple example message
Date: Fri, 07 Oct 2022 16:44:37 +0200
Message-ID: <unique-identifier@example.org>
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 := "TEST\r\n"
),然后又写了一个换行符 (buf.WriteString(fmt.Sprintf("Subject: %s\r\n", mail.Subject))
)。因此,MIME-Version: 1.0
被认为是消息正文的一部分,而不是头部。请再试一次,去掉 title
末尾的换行符。正如 Steffen Ullrich 指出的,当你发送 PDF 时,应将 Content-Type: text/plain; charset="utf-8"
替换为 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 <alice@example.org>
To: Bob <bob@example.com>
Cc: Carol <carol@example.com>
Subject: A simple example message
Date: Fri, 07 Oct 2022 16:44:37 +0200
Message-ID: <unique-identifier@example.org>
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 := "TEST\r\n"
) and then write another newline (buf.WriteString(fmt.Sprintf("Subject: %s\r\n", 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="utf-8"
with Content-Type: application/pdf
when you send a PDF. (The charset is also not necessary.)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论