Home > Software design >  Sending an email with RAW content using AWS Pinpoint and Go returns 403
Sending an email with RAW content using AWS Pinpoint and Go returns 403

Time:04-12

I'm trying to send an email via AWS pinpoint containing attachments. To send attachments with an email, you must use the 'RAW' email content. The only documentation I can find about this is here: https://docs.aws.amazon.com/pinpoint-email/latest/APIReference/API_RawMessage.html, but it is missing quite a few things (like, what are the required headers?)

When I send an email using the 'simple' content, it works fine:

emailInput := &pinpointemail.SendEmailInput{
    Destination: &pinpointemail.Destination{
        ToAddresses: []*string{&address},
    },
    FromEmailAddress: &sender,
    Content: &pinpointemail.EmailContent{
                Simple: &pinpointemail.Message{
                    Body: &pinpointemail.Body{
                        Html: &pinpointemail.Content{
                            Charset: &charset,
                            Data:    &emailHTML,
                        },
                        Text: &pinpointemail.Content{
                            Charset: &charset,
                            Data:    &emailText,
                        },
                    },
                    Subject: &pinpointemail.Content{
                        Charset: &charset,
                        Data:    &emailSubject,
                    },
                },
}

Since I want to add attachments, I have to use the 'RAW' content type. I have written a function which generates the email content, based on: https://gist.github.com/douglasmakey/90753ecf37ac10c25873825097f46300:

func generateRawEmailContent(subject, to, from, HTMLBody string, attachments *[]EmailAttachment) []byte {
    buf := bytes.NewBuffer(nil)
    buf.WriteString(fmt.Sprintf("Subject: %s\n", subject))
    buf.WriteString(fmt.Sprintf("To: %s\n", to))
    buf.WriteString(fmt.Sprintf("From: %s\n\n", from))

    buf.WriteString("MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n")
    buf.WriteString(HTMLBody)

    writer := multipart.NewWriter(buf)
    boundary := writer.Boundary()

    buf.WriteString(fmt.Sprintf("Content-Type: multipart/mixed; boundary=%s\n", boundary))
    buf.WriteString(fmt.Sprintf("--%s\n", boundary))

    for _, attachment := range *attachments {
        buf.WriteString(fmt.Sprintf("\n\n--%s\n", boundary))
        buf.WriteString(fmt.Sprintf("Content-Type: %s\n", http.DetectContentType(attachment.Data)))
        buf.WriteString("Content-Transfer-Encoding: base64\n")
        buf.WriteString(fmt.Sprintf("Content-Disposition: attachment; filename=%s\n", attachment.FileName))

        b := make([]byte, base64.StdEncoding.EncodedLen(len(attachment.Data)))
        base64.StdEncoding.Encode(b, attachment.Data)
        buf.Write(b)
        buf.WriteString(fmt.Sprintf("\n--%s", boundary))
    }

    buf.WriteString("--")

    log.Println(string(buf.Bytes()))

    return buf.Bytes()
}

This generates the following (emails changed):

Subject: Welcome \nTo: [email protected]\nFrom: [email protected]\n\nMIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n\u003ch1\u003eHello ,\u003c/h1\u003e\u003cp\u003eYou now have an account.\u003c/p\u003e\nContent-Type: multipart/mixed; boundary=8f6b2cc498b79f5a99550b930ba1ecab1fc1ee2d3425a0a69ab67b83b647\n--8f6b2cc498b79f5a99550b930ba1ecab1fc1ee2d3425a0a69ab67b83b647\n\n\n--8f6b2cc498b79f5a99550b930ba1ecab1fc1ee2d3425a0a69ab67b83b647\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: base64\nContent-Disposition: attachment; filename=test.json\newogICJ0ZXN0IjogdHJ1ZQp9\n--8f6b2cc498b79f5a99550b930ba1ecab1fc1ee2d3425a0a69ab67b83b647--

I then construct the email as follows:

&pinpointemail.SendEmailInput{
    Destination: &pinpointemail.Destination{
        ToAddresses: []*string{&address},
    },
    FromEmailAddress: &sender,
    Content: &pinpointemail.EmailContent{
                Raw: &pinpointemail.RawMessage{
                    Data: generateRawEmailContent(emailSubject, address, sender, emailHTML, emailAttachments),
                },
}

When sending this email via the github.com/aws/aws-sdk-go/service/pinpoint package, I get a 403 returned, and I have no idea why. A 403 means that the resource I'm trying to access is forbidden, but I don't see how that is relevant here? Also, there is no documentation about a 403 even being a possible response. Any help would be greatly appreciated!

I have also tried using libraries, like for instance the gomail-v2 library as follows:

m := gomail.NewMessage()
    m.SetHeader("From", from)
    m.SetHeader("To", to)
    m.SetHeader("Subject", subject)
    m.SetBody("text/plain", textBody)
    m.AddAlternative("text/html", HTMLBody)
    m.Attach("foo.txt", gomail.SetCopyFunc(func(w io.Writer) error {
        _, err := w.Write((*attachments)[0].Data)
        return err
    }))

    buf := bytes.NewBuffer(make([]byte, 0, 2048))
    _, werr := m.WriteTo(buf)
    if werr != nil {
        return nil, common.NewStackError(werr)
    }

But that still gives me a 403 error.

CodePudding user response:

I'm not a Go person, so this is just a brutal attempt to shuffle around code lines to hopefully produce a valid MIME structure.

func generateRawEmailContent(subject, to, from, HTMLBody string, attachments *[]EmailAttachment) []byte {
    buf := bytes.NewBuffer(nil)
    // Creating headers by gluing together strings is precarious.
    // I'm sure there must be a better way.
    buf.WriteString(fmt.Sprintf("Subject: %s\n", subject))
    buf.WriteString(fmt.Sprintf("To: %s\n", to))
    // Remove spurious newline
    buf.WriteString(fmt.Sprintf("From: %s\n", from))

    writer := multipart.NewWriter(buf)
    boundary := writer.Boundary()

    buf.WriteString(fmt.Sprintf("MIME-Version: 1.0\n", boundary))
    buf.WriteString(fmt.Sprintf("Content-Type: multipart/mixed; boundary=%s\n", boundary))
    // End of headers
    buf.WriteString("\n")

    buf.WriteString(fmt.Sprintf("--%s\n", boundary))

    buf.WriteString("Content-Type: text/html; charset=\"UTF-8\";\n\n")
    buf.WriteString(HTMLBody)

    for _, attachment := range *attachments {
        buf.WriteString(fmt.Sprintf("\n\n--%s\n", boundary))
        buf.WriteString(fmt.Sprintf("Content-Type: %s\n", http.DetectContentType(attachment.Data)))
        buf.WriteString("Content-Transfer-Encoding: base64\n")
        buf.WriteString(fmt.Sprintf("Content-Disposition: attachment; filename=%s\n", attachment.FileName))

        b := make([]byte, base64.StdEncoding.EncodedLen(len(attachment.Data)))
        base64.StdEncoding.Encode(b, attachment.Data)
        buf.Write(b)
        // Don't add a second boundary here
        buf.WriteString("\n")
    }

    // Final terminating boundary, notice -- after
    buf.WriteString(fmt.Sprintf("\n--%s--\n", boundary))

    log.Println(string(buf.Bytes()))

    return buf.Bytes()
}

The resulting output should look something like

Subject: subject
To: recipient <[email protected]>
From: me <[email protected]>
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary=foobar

--foobar
Content-Type: text/html; charset="UTF-8"

<h1>Tremble, victim</h1>
<p>We don't send <tt>text/plain</tt> because we
hate our users.</p>

--foobar
Content-Type: application/octet-stream
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=skull_crossbones.jpg

YmluYXJ5ZGF0YQ==
--foobar--

CodePudding user response:

Okay, found the issue. Turns out that this 403 error has nothing to do with my code, but rather with IAM permissions in AWS. Apparently an IAM permission has to be turned on to enable RAW email content.

  • Related