You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Go与AWS SES v2实现带附件的原始邮件发送接口

用Go实现带附件的表单提交邮件发送端点

直接上可运行的完整方案,核心是利用Go标准库处理表单上传、构建MIME邮件与发送,完全不用手动拼接原始MIME内容:

package main

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"io"
	"net/http"
	"net/mail"
	"net/smtp"
	"mime"
	"mime/multipart"
	"path/filepath"
)

func main() {
	http.HandleFunc("/submit-form", handleFormSubmit)
	fmt.Println("Server starting on :8080...")
	http.ListenAndServe(":8080", nil)
}

func handleFormSubmit(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	// 解析multipart表单,设置10MB内存上限,超出内容自动写入临时文件
	err := r.ParseMultipartForm(10 << 20)
	if err != nil {
		http.Error(w, "Failed to parse form", http.StatusBadRequest)
		return
	}

	// 提取表单字段
	name := r.FormValue("name")
	email := r.FormValue("email")
	phone := r.FormValue("phone")
	message := r.FormValue("message")

	// 验证必填项
	if name == "" || email == "" || message == "" {
		http.Error(w, "Name, email and message are required", http.StatusBadRequest)
		return
	}

	// 按要求格式化邮件正文
	emailBody := fmt.Sprintf("%s (%s) 说:%s", name, email, message)

	// SMTP配置(根据你的邮件服务商修改)
	smtpHost := "smtp.yourprovider.com"
	smtpPort := "587"
	smtpUser := "your-email@example.com"
	smtpPass := "your-password-or-app-password"
	to := []string{"site@mysite.co.uk"}

	// 创建邮件内容缓冲区
	var emailBuffer bytes.Buffer
	writer := multipart.NewWriter(&emailBuffer)

	// 构建邮件头
	from := mail.Address{Name: "Website Form", Address: smtpUser}
	headers := map[string]string{
		"From":         from.String(),
		"To":           to[0],
		"Subject":      fmt.Sprintf("新表单提交 - %s", name),
		"MIME-Version": "1.0",
		"Content-Type": fmt.Sprintf("multipart/mixed; boundary=%s", writer.Boundary()),
	}

	// 写入邮件头到缓冲区
	for k, v := range headers {
		fmt.Fprintf(&emailBuffer, "%s: %s\r\n", k, v)
	}
	fmt.Fprintf(&emailBuffer, "\r\n")

	// 添加文本正文部分
	textPart, err := writer.CreatePart(map[string][]string{
		"Content-Type": {"text/plain; charset=utf-8"},
	})
	if err != nil {
		http.Error(w, "Failed to create text part", http.StatusInternalServerError)
		return
	}
	textPart.Write([]byte(emailBody))

	// 处理上传的照片(前端表单字段名需为"photos")
	files := r.MultipartForm.File["photos"]
	if len(files) > 5 {
		http.Error(w, "Max 5 photos allowed", http.StatusBadRequest)
		return
	}

	for _, fileHeader := range files {
		file, err := fileHeader.Open()
		if err != nil {
			http.Error(w, fmt.Sprintf("Failed to open file %s", fileHeader.Filename), http.StatusInternalServerError)
			return
		}
		defer file.Close()

		// 自动检测文件MIME类型, fallback为二进制流
		fileType := mime.TypeByExtension(filepath.Ext(fileHeader.Filename))
		if fileType == "" {
			fileType = "application/octet-stream"
		}

		// 创建附件部分
		attachmentPart, err := writer.CreatePart(map[string][]string{
			"Content-Type":                {fileType},
			"Content-Disposition":         {fmt.Sprintf("attachment; filename=\"%s\"", fileHeader.Filename)},
			"Content-Transfer-Encoding":   {"base64"},
		})
		if err != nil {
			http.Error(w, fmt.Sprintf("Failed to create attachment for %s", fileHeader.Filename), http.StatusInternalServerError)
			return
		}

		// 将文件内容base64编码后写入附件
		encoder := base64.NewEncoder(base64.StdEncoding, attachmentPart)
		_, err = io.Copy(encoder, file)
		encoder.Close()
		if err != nil {
			http.Error(w, fmt.Sprintf("Failed to write attachment %s", fileHeader.Filename), http.StatusInternalServerError)
			return
		}
	}

	// 结束multipart内容写入
	writer.Close()

	// SMTP认证与发送
	auth := smtp.PlainAuth("", smtpUser, smtpPass, smtpHost)
	err = smtp.SendMail(smtpHost+":"+smtpPort, auth, smtpUser, to, emailBuffer.Bytes())
	if err != nil {
		http.Error(w, fmt.Sprintf("Failed to send email: %v", err), http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, "Form submitted successfully")
}

核心细节说明

  1. 表单处理:用r.ParseMultipartForm()处理带文件的表单,设置内存上限避免溢出,超出内容自动写入临时文件。
  2. MIME邮件构建:
    • 用multipart.Writer自动生成MIME边界,完全不用手动拼接复杂的原始MIME内容,避免格式错误。
    • 分步骤添加文本正文和附件,每个部分通过CreatePart设置对应的HTTP头,符合MIME规范。
    • 附件采用base64编码,避免二进制内容传输乱码。
  3. SMTP配置:根据你的邮件服务商修改参数,比如Gmail用smtp.gmail.com:587+应用密码,QQ邮箱用smtp.qq.com:587+授权码。
  4. 边界校验:限制照片数量、验证必填字段,避免无效请求。

生产环境注意事项

  • 前端表单必须设置enctype="multipart/form-data",否则无法上传文件。
  • 照片字段的name属性要和代码中r.MultipartForm.File["photos"]一致。
  • 添加文件类型校验(仅允许jpg/png等图片格式),避免恶意文件上传。
  • SMTP密码不要硬编码,改用环境变量读取。

内容的提问来源于stack exchange,提问作者J86

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 23:47:08