使用AWS Pinpoint和Go发送包含原始内容的电子邮件返回403错误。

huangapple go评论78阅读模式
英文:

Sending an email with RAW content using AWS Pinpoint and Go returns 403

问题

我正在尝试通过AWS Pinpoint发送包含附件的电子邮件。要发送带附件的电子邮件,您必须使用“RAW”电子邮件内容。我能找到的唯一文档是这里:https://docs.aws.amazon.com/pinpoint-email/latest/APIReference/API_RawMessage.html,但它缺少一些重要信息(例如,必需的标头是什么?)

当我使用“简单”内容发送电子邮件时,一切正常:

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,
					},
				},
}

由于我想添加附件,我必须使用“RAW”内容类型。我编写了一个函数,根据以下内容生成电子邮件内容: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()
}

这将生成以下内容(电子邮件已更改):

Subject: Welcome \nTo: xxxxx@gmail.com\nFrom: xxxxx@gmail.com\n\nMIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n<h1>Hello ,</h1><p>You now have an account.</p>\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--

然后,我按以下方式构建电子邮件:

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

当通过github.com/aws/aws-sdk-go/service/pinpoint包发送此电子邮件时,我收到403错误,但我不知道原因。403表示我尝试访问的资源被禁止,但我不明白这与此处的问题有何关系?而且,甚至没有关于403错误的可能响应的文档。任何帮助将不胜感激!

我还尝试使用库,例如gomail-v2库,如下所示:

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)
	}

但仍然出现403错误。

英文:

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 := &amp;pinpointemail.SendEmailInput{
	Destination: &amp;pinpointemail.Destination{
		ToAddresses: []*string{&amp;address},
	},
	FromEmailAddress: &amp;sender,
    Content: &amp;pinpointemail.EmailContent{
				Simple: &amp;pinpointemail.Message{
					Body: &amp;pinpointemail.Body{
						Html: &amp;pinpointemail.Content{
							Charset: &amp;charset,
							Data:    &amp;emailHTML,
						},
						Text: &amp;pinpointemail.Content{
							Charset: &amp;charset,
							Data:    &amp;emailText,
						},
					},
					Subject: &amp;pinpointemail.Content{
						Charset: &amp;charset,
						Data:    &amp;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(&quot;Subject: %s\n&quot;, subject))
	buf.WriteString(fmt.Sprintf(&quot;To: %s\n&quot;, to))
	buf.WriteString(fmt.Sprintf(&quot;From: %s\n\n&quot;, from))

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

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

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

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

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

	buf.WriteString(&quot;--&quot;)

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

	return buf.Bytes()
}

This generates the following (emails changed):

Subject: Welcome \nTo: xxxxx@gmail.com\nFrom: xxxxx@gmail.com\n\nMIME-version: 1.0;\nContent-Type: text/html; charset=\&quot;UTF-8\&quot;;\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:

&amp;pinpointemail.SendEmailInput{
	Destination: &amp;pinpointemail.Destination{
		ToAddresses: []*string{&amp;address},
	},
	FromEmailAddress: &amp;sender,
    Content: &amp;pinpointemail.EmailContent{
				Raw: &amp;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(&quot;From&quot;, from)
	m.SetHeader(&quot;To&quot;, to)
	m.SetHeader(&quot;Subject&quot;, subject)
	m.SetBody(&quot;text/plain&quot;, textBody)
	m.AddAlternative(&quot;text/html&quot;, HTMLBody)
	m.Attach(&quot;foo.txt&quot;, 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.

答案1

得分: 1

我不是Go语言专家,所以这只是一个粗暴的尝试,将代码行重新排列,希望能生成一个有效的MIME结构。

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", 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))
    // 头部结束
    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)
        // 这里不要添加第二个边界
        buf.WriteString("\n")
    }

    // 最终的结束边界,注意--之后
    buf.WriteString(fmt.Sprintf("\n--%s--\n", boundary))

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

    return buf.Bytes()
}

生成的输出应该类似于:

Subject: subject
To: recipient <victim@example.org>
From: me <sender@example.net>
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--
英文:

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&#39;m sure there must be a better way.
buf.WriteString(fmt.Sprintf(&quot;Subject: %s\n&quot;, subject))
buf.WriteString(fmt.Sprintf(&quot;To: %s\n&quot;, to))
// Remove spurious newline
buf.WriteString(fmt.Sprintf(&quot;From: %s\n&quot;, from))
writer := multipart.NewWriter(buf)
boundary := writer.Boundary()
buf.WriteString(fmt.Sprintf(&quot;MIME-Version: 1.0\n&quot;, boundary))
buf.WriteString(fmt.Sprintf(&quot;Content-Type: multipart/mixed; boundary=%s\n&quot;, boundary))
// End of headers
buf.WriteString(&quot;\n&quot;)
buf.WriteString(fmt.Sprintf(&quot;--%s\n&quot;, boundary))
buf.WriteString(&quot;Content-Type: text/html; charset=\&quot;UTF-8\&quot;;\n\n&quot;)
buf.WriteString(HTMLBody)
for _, attachment := range *attachments {
buf.WriteString(fmt.Sprintf(&quot;\n\n--%s\n&quot;, boundary))
buf.WriteString(fmt.Sprintf(&quot;Content-Type: %s\n&quot;, http.DetectContentType(attachment.Data)))
buf.WriteString(&quot;Content-Transfer-Encoding: base64\n&quot;)
buf.WriteString(fmt.Sprintf(&quot;Content-Disposition: attachment; filename=%s\n&quot;, attachment.FileName))
b := make([]byte, base64.StdEncoding.EncodedLen(len(attachment.Data)))
base64.StdEncoding.Encode(b, attachment.Data)
buf.Write(b)
// Don&#39;t add a second boundary here
buf.WriteString(&quot;\n&quot;)
}
// Final terminating boundary, notice -- after
buf.WriteString(fmt.Sprintf(&quot;\n--%s--\n&quot;, boundary))
log.Println(string(buf.Bytes()))
return buf.Bytes()
}

The resulting output should look something like

Subject: subject
To: recipient &lt;victim@example.org&gt;
From: me &lt;sender@example.net&gt;
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary=foobar
--foobar
Content-Type: text/html; charset=&quot;UTF-8&quot;
&lt;h1&gt;Tremble, victim&lt;/h1&gt;
&lt;p&gt;We don&#39;t send &lt;tt&gt;text/plain&lt;/tt&gt; because we
hate our users.&lt;/p&gt;
--foobar
Content-Type: application/octet-stream
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=skull_crossbones.jpg
YmluYXJ5ZGF0YQ==
--foobar--

答案2

得分: 1

好的,我明白了。以下是翻译好的内容:

好的,找到问题了。原来这个403错误与我的代码无关,而是与AWS中的IAM权限有关。显然,必须打开一个IAM权限才能启用原始电子邮件内容。

英文:

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.

huangapple
  • 本文由 发表于 2022年4月11日 16:49:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/71825024.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定