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

Time:10-10

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 (
    "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{"[email protected]"}
    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.

CodePudding user response:

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 <[email protected]>
To: Bob <[email protected]>
Cc: Carol <[email protected]>
Subject: A simple example message
Date: Fri, 07 Oct 2022 16:44:37  0200
Message-ID: <[email protected]>
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.)

  • Related