package service import ( "bytes" "encoding/base64" "fmt" "mime/quotedprintable" "strings" sestypes "github.com/aws/aws-sdk-go-v2/service/sesv2/types" "wucher/internal/queue" "wucher/internal/shared/pkg/uuidv7" ) func buildSESRawContent(from string, to, cc, replyTo []string, job queue.EmailJob) (*sestypes.EmailContent, error) { mixedBoundary := randomMIMEBoundary("mixed") relatedBoundary := randomMIMEBoundary("related") alternativeBoundary := randomMIMEBoundary("alt") var buf bytes.Buffer writeMIMEHeader(&buf, "From", from) if len(to) > 0 { writeMIMEHeader(&buf, "To", strings.Join(to, ", ")) } if len(cc) > 0 { writeMIMEHeader(&buf, "Cc", strings.Join(cc, ", ")) } if len(replyTo) > 0 { writeMIMEHeader(&buf, "Reply-To", strings.Join(replyTo, ", ")) } writeMIMEHeader(&buf, "Subject", sanitizeSubject(job.Subject)) writeMIMEHeader(&buf, "MIME-Version", "1.0") writeMIMEHeader(&buf, "Content-Type", fmt.Sprintf(`multipart/mixed; boundary="%s"`, mixedBoundary)) buf.WriteString("\r\n") writeBoundary(&buf, mixedBoundary) writeMIMEHeader(&buf, "Content-Type", fmt.Sprintf(`multipart/related; boundary="%s"`, relatedBoundary)) buf.WriteString("\r\n") writeBoundary(&buf, relatedBoundary) writeMIMEHeader(&buf, "Content-Type", fmt.Sprintf(`multipart/alternative; boundary="%s"`, alternativeBoundary)) buf.WriteString("\r\n") if textBody := strings.TrimSpace(job.TextBodyValue()); textBody != "" { writeBoundary(&buf, alternativeBoundary) writeTextPart(&buf, "text/plain", textBody) } if htmlBody := strings.TrimSpace(job.HTMLBody); htmlBody != "" { writeBoundary(&buf, alternativeBoundary) writeTextPart(&buf, "text/html", htmlBody) } writeClosingBoundary(&buf, alternativeBoundary) for i := range job.Attachments { writeBoundary(&buf, relatedBoundary) writeAttachmentPart(&buf, job.Attachments[i]) } writeClosingBoundary(&buf, relatedBoundary) writeClosingBoundary(&buf, mixedBoundary) return &sestypes.EmailContent{ Raw: &sestypes.RawMessage{Data: buf.Bytes()}, }, nil } func writeTextPart(buf *bytes.Buffer, contentType, body string) { writeMIMEHeader(buf, "Content-Type", contentType+`; charset="UTF-8"`) writeMIMEHeader(buf, "Content-Transfer-Encoding", "quoted-printable") buf.WriteString("\r\n") qp := quotedprintable.NewWriter(buf) _, _ = qp.Write([]byte(body)) _ = qp.Close() buf.WriteString("\r\n") } func writeAttachmentPart(buf *bytes.Buffer, attachment queue.EmailAttachment) { filename := attachment.Filename if filename == "" { filename = "attachment" } writeMIMEHeader(buf, "Content-Type", fmt.Sprintf(`%s; name="%s"`, attachment.ContentType, escapeMIMEHeaderValue(filename))) writeMIMEHeader(buf, "Content-Transfer-Encoding", "base64") disposition := "attachment" if attachment.Inline { disposition = "inline" } writeMIMEHeader(buf, "Content-Disposition", fmt.Sprintf(`%s; filename="%s"`, disposition, escapeMIMEHeaderValue(filename))) if attachment.Inline && strings.TrimSpace(attachment.ContentID) != "" { writeMIMEHeader(buf, "Content-ID", "<"+strings.TrimSpace(attachment.ContentID)+">") } buf.WriteString("\r\n") writeBase64Lines(buf, attachment.Content) } func writeBase64Lines(buf *bytes.Buffer, payload []byte) { encoded := base64.StdEncoding.EncodeToString(payload) for len(encoded) > 76 { buf.WriteString(encoded[:76]) buf.WriteString("\r\n") encoded = encoded[76:] } if encoded != "" { buf.WriteString(encoded) buf.WriteString("\r\n") } } func writeMIMEHeader(buf *bytes.Buffer, key, value string) { buf.WriteString(key) buf.WriteString(": ") buf.WriteString(value) buf.WriteString("\r\n") } func writeBoundary(buf *bytes.Buffer, boundary string) { buf.WriteString("--") buf.WriteString(boundary) buf.WriteString("\r\n") } func writeClosingBoundary(buf *bytes.Buffer, boundary string) { buf.WriteString("--") buf.WriteString(boundary) buf.WriteString("--\r\n") } func randomMIMEBoundary(prefix string) string { id, err := uuidv7.String(uuidv7.MustNew()) if err != nil { return prefix + "_fallback" } return prefix + "_" + strings.ReplaceAll(id, "-", "") } func escapeMIMEHeaderValue(value string) string { return strings.ReplaceAll(strings.TrimSpace(value), `"`, `'`) }