基于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") }
核心细节说明
- 表单处理:用
r.ParseMultipartForm()处理带文件的表单,设置内存上限避免溢出,超出内容自动写入临时文件。 - MIME邮件构建:
- 用
multipart.Writer自动生成MIME边界,完全不用手动拼接复杂的原始MIME内容,避免格式错误。 - 分步骤添加文本正文和附件,每个部分通过
CreatePart设置对应的HTTP头,符合MIME规范。 - 附件采用base64编码,避免二进制内容传输乱码。
- 用
- SMTP配置:根据你的邮件服务商修改参数,比如Gmail用
smtp.gmail.com:587+应用密码,QQ邮箱用smtp.qq.com:587+授权码。 - 边界校验:限制照片数量、验证必填字段,避免无效请求。
生产环境注意事项
- 前端表单必须设置
enctype="multipart/form-data",否则无法上传文件。 - 照片字段的name属性要和代码中
r.MultipartForm.File["photos"]一致。 - 添加文件类型校验(仅允许jpg/png等图片格式),避免恶意文件上传。
- SMTP密码不要硬编码,改用环境变量读取。
内容的提问来源于stack exchange,提问作者J86
相关产品推荐
相关产品推荐

