Search code examples
gosmtpemail-attachmentsmime

Email message with no body part when attaching a file and sending via SMTP in Golang


I am trying to send an email message with both an email body and a file attachment (a CSV file) in Go (Golang).

I am following the mime standard of a multi-part message, however I am not very familiar with the structure of the messages following that standard. I am vaguely following a Python code snippet from a colleague as a guide which is using the Python library email (I think this is from the standard library) e.g. MIMEText and MIMEMultipart.

The email message body is not showing up when executing the following Go code:

  • What is wrong with it?
  • How can I send an email message with both that file attachment and an email message body?

This function should return a byte slice to be used as a parameter for the call to smtp.SendMail from the Go standard library. See the comments below explaining what's happening to the received email message (the THIS DOES NOT SHOW UP [...] and the 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
}

Solution

  • Coincidentally I had a similar issue the other day. I needed a blank line between the body content type and the beginning of the body itself. Below is the updated lines for this section of your code:

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

    For clarity, this newline (\r\n) doesn't have to be exactly here, it can be appended to the content-type line above. It just needs to see a blank line between the content-type and the beginning of the body.

    I'm assuming the attachment is being attached without problems, correct? My assumption is this is because you have the double new-line at the end of the content-disposition line before the attachment data is added.