init push
This commit is contained in:
138
internal/service/email_raw_mime.go
Normal file
138
internal/service/email_raw_mime.go
Normal file
@@ -0,0 +1,138 @@
|
||||
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), `"`, `'`)
|
||||
}
|
||||
Reference in New Issue
Block a user