使用Go通过AWS SES v2发送带附件的原始电子邮件

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

Sending a raw email with attachments in Go via AWS SES v2

问题

我正在尝试创建一个HTTP端点,用于处理来自网站的表单提交。

该表单具有以下字段:

  • 姓名
  • 电子邮件
  • 电话
  • 电子邮件正文(邮件正文的文本)
  • 照片(最多5张)

然后,我的端点将向site@mysite.co.uk发送一封带有附件照片的电子邮件,邮件正文可能如下所示:

> John (john@example.com) 说:
>
> 邮件正文...

我对Go还不熟悉,但我已经尝试了两个星期,但仍然没有成功。

我目前的代码如下:

package aj

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"io/ioutil"
	"mime"
	"net/http"
	"net/mail"
	"net/textproto"
	"os"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/service/sesv2"
	"github.com/aws/aws-sdk-go-v2/service/sesv2/types"
	"go.uber.org/zap"
)

const expectedContentType string = "multipart/form-data"
const charset string = "UTF-8"

func FormSubmissionHandler(logger *zap.Logger, emailSender EmailSender) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		logger.Info("running the form submission handler...")

		// 获取目标电子邮件地址
		destinationEmail := os.Getenv("DESTINATION_EMAIL")

		// 获取电子邮件的主题行
		emailSubject := os.Getenv("EMAIL_SUBJECT")

		// 强制要求使用multipart/form-data内容类型
		contentType := r.Header.Get("content-type")

		mediatype, _, err := mime.ParseMediaType(contentType)
		if err != nil {
			logger.Error("解析MIME类型时出错", zap.Error(err))
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		if mediatype != expectedContentType {
			logger.Error("不支持的内容类型", zap.Error(err))
			http.Error(w, fmt.Sprintf("API预期的内容类型为%v", expectedContentType), http.StatusUnsupportedMediaType)
			return
		}

		err = r.ParseMultipartForm(10 << 20)
		if err != nil {
			logger.Error("解析表单数据时出错", zap.Error(err))
			http.Error(w, "解析表单数据时出错", http.StatusBadRequest)
			return
		}

		name := r.MultipartForm.Value["name"]
		if len(name) == 0 {
			logger.Error("未设置姓名", zap.Error(err))
			http.Error(w, "API要求设置姓名", http.StatusBadRequest)
			return
		}

		email := r.MultipartForm.Value["email"]
		if len(email) == 0 {
			logger.Error("未设置电子邮件", zap.Error(err))
			http.Error(w, "API要求设置电子邮件", http.StatusBadRequest)
			return
		}

		phone := r.MultipartForm.Value["phone"]
		if len(phone) == 0 {
			logger.Error("未设置电话", zap.Error(err))
			http.Error(w, "API要求设置电话", http.StatusBadRequest)
			return
		}

		body := r.MultipartForm.Value["body"]
		if len(body) == 0 {
			logger.Error("未设置正文", zap.Error(err))
			http.Error(w, "API要求设置正文", http.StatusBadRequest)
			return
		}

		files := r.MultipartForm.File["photos"]
		if len(files) == 0 {
			logger.Error("未提交任何文件", zap.Error(err))
			http.Error(w, "API要求提交一个或多个文件", http.StatusBadRequest)
			return
		}

		emailService := NewEmailService()

		sendEmailInput := sesv2.SendEmailInput{}

		destination := &types.Destination{
			ToAddresses: []string{destinationEmail},
		}

		// 将附件添加到电子邮件中
		for _, file := range files {
			f, err := file.Open()
			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
			defer f.Close()

			// 不确定如何在此处获取带有附件的电子邮件
		}

		message := &types.RawMessage{
			Data: make([]byte, 0), // 这必须更改为原始消息的字节
		}

		content := &types.EmailContent{
			Raw: message,
		}

		sendEmailInput.Content = content
		sendEmailInput.Destination = destination
		sendEmailInput.FromEmailAddress = aws.String(email[0])

		err = emailService.SendEmail(logger, r.Context(), &sendEmailInput)
		if err != nil {
			logger.Error("发送电子邮件时出错", zap.Error(err))
			http.Error(w, "发送电子邮件时出错", http.StatusBadRequest)
			return
		}

		w.WriteHeader(http.StatusOK)
	})
}

我的理解是(如果我理解错了,请纠正我),我必须构建一个类似于这个链接中的原始消息格式。假设我是正确的,我只是不知道如何在Go中实现这一点。

英文:

I am trying to create an HTTP endpoint that will handle a form submission from a website.

The form has the following fields:

  • Name
  • Email
  • Phone
  • Email body (text for the body of the email)
  • Photos (up to 5 of them)

My endpoint will then fire off an email to site@mysite.co.uk with the photos as attachments and the email body being something like:

> John (john@example.com) says:
>
> Email body ...

I am new to Go, but I've been trying to get this working for 2 weeks, and still haven't had any luck.

My code at the moment is:

package aj
import (
&quot;bytes&quot;
&quot;encoding/base64&quot;
&quot;fmt&quot;
&quot;io/ioutil&quot;
&quot;mime&quot;
&quot;net/http&quot;
&quot;net/mail&quot;
&quot;net/textproto&quot;
&quot;os&quot;
&quot;github.com/aws/aws-sdk-go-v2/aws&quot;
&quot;github.com/aws/aws-sdk-go-v2/service/sesv2&quot;
&quot;github.com/aws/aws-sdk-go-v2/service/sesv2/types&quot;
&quot;go.uber.org/zap&quot;
)
const expectedContentType string = &quot;multipart/form-data&quot;
const charset string = &quot;UTF-8&quot;
func FormSubmissionHandler(logger *zap.Logger, emailSender EmailSender) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
logger.Info(&quot;running the form submission handler...&quot;)
// get the destination email address
destinationEmail := os.Getenv(&quot;DESTINATION_EMAIL&quot;)
// get the subject line of the email
emailSubject := os.Getenv(&quot;EMAIL_SUBJECT&quot;)
// enforce a multipart/form-data content-type
contentType := r.Header.Get(&quot;content-type&quot;)
mediatype, _, err := mime.ParseMediaType(contentType)
if err != nil {
logger.Error(&quot;error when parsing the mime type&quot;, zap.Error(err))
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if mediatype != expectedContentType {
logger.Error(&quot;unsupported content-type&quot;, zap.Error(err))
http.Error(w, fmt.Sprintf(&quot;api expects %v content-type&quot;, expectedContentType), http.StatusUnsupportedMediaType)
return
}
err = r.ParseMultipartForm(10 &lt;&lt; 20)
if err != nil {
logger.Error(&quot;error parsing form data&quot;, zap.Error(err))
http.Error(w, &quot;error parsing form data&quot;, http.StatusBadRequest)
return
}
name := r.MultipartForm.Value[&quot;name&quot;]
if len(name) == 0 {
logger.Error(&quot;name not set&quot;, zap.Error(err))
http.Error(w, &quot;api expects name to be set&quot;, http.StatusBadRequest)
return
}
email := r.MultipartForm.Value[&quot;email&quot;]
if len(email) == 0 {
logger.Error(&quot;email not set&quot;, zap.Error(err))
http.Error(w, &quot;api expects email to be set&quot;, http.StatusBadRequest)
return
}
phone := r.MultipartForm.Value[&quot;phone&quot;]
if len(phone) == 0 {
logger.Error(&quot;phone not set&quot;, zap.Error(err))
http.Error(w, &quot;api expects phone to be set&quot;, http.StatusBadRequest)
return
}
body := r.MultipartForm.Value[&quot;body&quot;]
if len(body) == 0 {
logger.Error(&quot;body not set&quot;, zap.Error(err))
http.Error(w, &quot;api expects body to be set&quot;, http.StatusBadRequest)
return
}
files := r.MultipartForm.File[&quot;photos&quot;]
if len(files) == 0 {
logger.Error(&quot;no files were submitted&quot;, zap.Error(err))
http.Error(w, &quot;api expects one or more files to be submitted&quot;, http.StatusBadRequest)
return
}
emailService := NewEmailService()
sendEmailInput := sesv2.SendEmailInput{}
destination := &amp;types.Destination{
ToAddresses: []string{destinationEmail},
}
// add the attachments to the email
for _, file := range files {
f, err := file.Open()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer f.Close()
// not sure what to do here to get the email with the attachements
}
message := &amp;types.RawMessage{
Data: make([]byte, 0), // This must change to be the bytes of the raw message
}
content := &amp;types.EmailContent{
Raw: message,
}
sendEmailInput.Content = content
sendEmailInput.Destination = destination
sendEmailInput.FromEmailAddress = aws.String(email[0])
err = emailService.SendEmail(logger, r.Context(), &amp;sendEmailInput)
if err != nil {
logger.Error(&quot;an error occured sending the email&quot;, zap.Error(err))
http.Error(w, &quot;error sending email&quot;, http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusOK)
})
}

My understanding is (please correct me if I'm wrong) that I have to build out a raw message in a format similar to this. Assuming that is right, I'm just not sure how to do that in Go

答案1

得分: 1

为了创建附件,您需要使用base64对消息的内容进行编码

这里是一个发送csv文件作为附件的示例:

import (
  // ...
  secretutils "github.com/alessiosavi/GoGPUtils/aws/secrets"
  sesutils "github.com/alessiosavi/GoGPUtils/aws/ses"
)

type MailConf struct {
	FromName string   `json:"from_name,omitempty"`
	FromMail string   `json:"from_mail,omitempty"`
	To       string   `json:"to,omitempty"`
	CC       []string `json:"cc,omitempty"`
}

func SendRawMail(filename string, data []byte) error {
	var mailConf MailConf
	if err := secretutils.UnmarshalSecret(os.Getenv("XXX_YOUR_SECRET_STORED_IN_AWS"), &mailConf); err != nil {
		return err
	}
	subject := fmt.Sprintf("Found errors for the following file: %s", filename)
	var carbonCopy string
	if len(mailConf.CC) > 0 {
		carbonCopy = stringutils.JoinSeparator(",", mailConf.CC...)
	} else {
		carbonCopy = ""
	}
	raw := fmt.Sprintf(`From: "%[1]s" <%[2]s>
To: %[3]s
Cc: %[4]s
Subject: %[5]s
Content-Type: multipart/mixed;
    boundary="1"

--1
Content-Type: multipart/alternative;
    boundary="sub_1"

--sub_1
Content-Type: string/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable

Please see the attached file for a list of errors

--sub_1
Content-Type: string/html; charset=utf-8
Content-Transfer-Encoding: quoted-printable

<html>
<head></head>
<body>
<h1>%[6]s</h1>
<p><h2>Please see the attached file for the list of the rows.<h2></p>
</body>
</html>

--sub_1--

--1
Content-Type: string/plain; name="errors_%[6]s"
Content-Description: errors_%[6]s
Content-Disposition: attachment;filename="errors_%[6]s";
creation-date="%[7]s";
Content-Transfer-Encoding: base64

%[8]s

--1--`, mailConf.FromName, mailConf.FromMail, mailConf.To, carbonCopy, subject, strings.Replace(filename, ".csv", ".json", 1), time.Now().Format("2-Jan-06 3.04.05 PM"), base64.StdEncoding.EncodeToString(data))

	return sesutils.SendMail([]byte(raw))
}


英文:

In order to create an attachment, you have to encode using base64 the content of the message.

Here an example that send a csv as attacchemnt:

import (
  // ...
  secretutils &quot;github.com/alessiosavi/GoGPUtils/aws/secrets&quot;
  sesutils &quot;github.com/alessiosavi/GoGPUtils/aws/ses&quot;
)

type MailConf struct {
	FromName string   `json:&quot;from_name,omitempty&quot;`
	FromMail string   `json:&quot;from_mail,omitempty&quot;`
	To       string   `json:&quot;to,omitempty&quot;`
	CC       []string `json:&quot;cc,omitempty&quot;`
}

func SendRawMail(filename string, data []byte) error {
	var mailConf MailConf
	if err := secretutils.UnmarshalSecret(os.Getenv(&quot;XXX_YOUR_SECRET_STORED_IN_AWS&quot;), &amp;mailConf); err != nil {
		return err
	}
	subject := fmt.Sprintf(&quot;Found errors for the following file: %s&quot;, filename)
	var carbonCopy string
	if len(mailConf.CC) &gt; 0 {
		carbonCopy = stringutils.JoinSeparator(&quot;,&quot;, mailConf.CC...)
	} else {
		carbonCopy = &quot;&quot;
	}
	raw := fmt.Sprintf(`From: &quot;%[1]s&quot; &lt;%[2]s&gt;
To: %[3]s
Cc: %[4]s
Subject: %[5]s
Content-Type: multipart/mixed;
    boundary=&quot;1&quot;

--1
Content-Type: multipart/alternative;
    boundary=&quot;sub_1&quot;

--sub_1
Content-Type: string/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable

Please see the attached file for a list of errors

--sub_1
Content-Type: string/html; charset=utf-8
Content-Transfer-Encoding: quoted-printable

&lt;html&gt;
&lt;head&gt;&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;%[6]s&lt;/h1&gt;
&lt;p&gt;&lt;h2&gt;Please see the attached file for the list of the rows.&lt;h2&gt;&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;

--sub_1--

--1
Content-Type: string/plain; name=&quot;errors_%[6]s&quot;
Content-Description: errors_%[6]s
Content-Disposition: attachment;filename=&quot;errors_%[6]s&quot;;
creation-date=&quot;%[7]s&quot;;
Content-Transfer-Encoding: base64

%[8]s

--1--`, mailConf.FromName, mailConf.FromMail, mailConf.To, carbonCopy, subject, strings.Replace(filename, &quot;.csv&quot;, &quot;.json&quot;, 1), time.Now().Format(&quot;2-Jan-06 3.04.05 PM&quot;), base64.StdEncoding.EncodeToString(data))

	return sesutils.SendMail([]byte(raw))
}


huangapple
  • 本文由 发表于 2023年3月12日 20:42:41
  • 转载请务必保留本文链接:https://go.coder-hub.com/75713184.html
匿名

发表评论

匿名网友

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

确定