Search code examples
csvgosmtpbase64email-attachments

SMTP email with CSV attachment - extra characters coming in at the end of file


I'm creating an email client using Golang, to send email with a CSV file attached. Everything is working fine except that in the received email attachment, I can see some unwanted extra characters at the end of the file.

My code snippet:


import (
    "bytes"
    "encoding/base64"
    "fmt"
    "mime/multipart"
    "net/smtp"

    ...
)

func SendEmail(cfg Config) error {

    body := bytes.NewBuffer(nil)
    body.WriteString(fmt.Sprintf("From: %s\n", cfg.EmailFrom))
    body.WriteString(fmt.Sprintf("To: %s\n", cfg.EmailTo))
    body.WriteString(fmt.Sprintf("Subject: %s\n", cfg.EmailSubject))

    // csv file to attach
    fileContents := `column1,column2,column3\nAAA,BBB,CCC\nDDD,EEE,FFF\n`
    fileContentBytes := []byte(fileContents)

    body.WriteString("MIME-Version: 1.0\n")
    writer := multipart.NewWriter(body)
    boundary := writer.Boundary()

    // attach file
    body.WriteString("Content-Type: text/plain\n")
    body.WriteString("Content-Transfer-Encoding: base64\n")
    body.WriteString(fmt.Sprintf("Content-Disposition: attachment; filename=%s\n", "test-filename"))

    encodedBytes := make([]byte, base64.StdEncoding.EncodedLen(len(fileContentBytes)))
    base64.StdEncoding.Encode(encodedBytes, fileContentBytes)
    body.Write(encodedBytes)
    body.WriteString(fmt.Sprintf("\n--%s--", boundary))

    err = smtp.SendMail(cfg.EmailSMTPHost+":"+cfg.EmailSMTPPort,
        nil, cfg.EmailFrom, []string{cfg.EmailTo}, body.Bytes())
    if err != nil {
        return errors.Wrap(err, "smtp.SendMail failed")
    }

    return nil
}

Expected csv file:

column1,column2,column3
AAA,BBB,CCC
DDD,EEE,FFF

Obtained csv file:

column1,column2,column3
AAA,BBB,CCC
DDD,EEE,FFF
5k§xõí»ã}8

Anything wrong in the file contents encoding? Any help will be appreciated, thanks!


Solution

  • This code has at least two problems: missing empty line to separate MIME header and body and then adding some MIME boundary at the end even though this is no multipart mail. Currently the created mail looks like this:

    From: me@example.com
    To: you@example.com
    Subject: test
    MIME-Version: 1.0
    Content-Type: text/plain
    Content-Transfer-Encoding: base64
    Content-Disposition: attachment; filename=test-filename
    Y29sdW1uMSxjb2x1bW4yLGNvbHVtbjNcbkFBQSxCQkIsQ0NDXG5EREQsRUVFLEZGRlxu
    --973d0754ef322150f1977af176c9e1917c6dea9dfa0390e8e99af038c086--
    

    The wrong boundary at the end gets decoded as base64 with invalid base64 characters like "-" simply being ignored. This causes the garbage at the end of the output.

    It should instead look like this as a single part. Note the missing (wrong) end-boundary and note the empty line between MIME header and body.

    From: me@example.com
    To: you@example.com
    Subject: test
    MIME-Version: 1.0
    Content-Type: text/plain
    Content-Transfer-Encoding: base64
    Content-Disposition: attachment; filename=test-filename
    
    Y29sdW1uMSxjb2x1bW4yLGNvbHVtbjNcbkFBQSxCQkIsQ0NDXG5EREQsRUVFLEZGRlxu
    

    Alternatively it should be done as a multipart mail as shown below. Note the different Content-Type in the main MIME header.

    From: me@example.com
    To: you@example.com
    Subject: test
    MIME-Version: 1.0
    Content-Type: multipart/mixed; 
       boundary=973d0754ef322150f1977af176c9e1917c6dea9dfa0390e8e99af038c086
    
    --973d0754ef322150f1977af176c9e1917c6dea9dfa0390e8e99af038c086
    Content-Type: text/plain
    Content-Transfer-Encoding: base64
    Content-Disposition: attachment; filename=test-filename
    
    Y29sdW1uMSxjb2x1bW4yLGNvbHVtbjNcbkFBQSxCQkIsQ0NDXG5EREQsRUVFLEZGRlxu
    --973d0754ef322150f1977af176c9e1917c6dea9dfa0390e8e99af038c086--