From d3189b0840702f8c4f34155afa8ee725df20f0c8 Mon Sep 17 00:00:00 2001 From: rmanach Date: Sat, 4 Nov 2023 15:45:59 +0100 Subject: [PATCH] add attachments to the email --- mail/mail.go | 96 +++++++++++++++++++++++++++++++++++++++------- services/sender.go | 7 +++- 2 files changed, 88 insertions(+), 15 deletions(-) diff --git a/mail/mail.go b/mail/mail.go index c02f177..146be23 100644 --- a/mail/mail.go +++ b/mail/mail.go @@ -1,9 +1,14 @@ package mail import ( + "bytes" + "encoding/base64" "encoding/json" "fmt" + "mime/multipart" + "net/http" "os" + "path/filepath" "strings" "github.com/go-playground/validator/v10" @@ -11,12 +16,34 @@ import ( var validate *validator.Validate +// getgetAttachmentsContent collects attachment data from a list of attachment paths. +func getAttachmentsContent(attachments []string) (map[string][]byte, error) { + if len(attachments) == 0 { + return nil, nil + } + + attachmentsData := map[string][]byte{} + + for _, attachmentPath := range attachments { + b, err := os.ReadFile(attachmentPath) + if err != nil { + return nil, err + } + + _, filename := filepath.Split(attachmentPath) + attachmentsData[filename] = b + } + + return attachmentsData, nil +} + type Email struct { - Path string - Sender string `json:"sender" validate:"required,email"` - Receivers string `json:"receivers" validate:"required"` - Subject string `json:"subject" validate:"required"` - Content string `json:"content" validate:"required"` + Path string + Sender string `json:"sender" validate:"required,email"` + Receivers string `json:"receivers" validate:"required"` + Subject string `json:"subject" validate:"required"` + Content string `json:"content" validate:"required"` + Attachments string `json:"attachments,omitempty"` } func FromJSON(path string) (Email, error) { @@ -42,15 +69,56 @@ func (e *Email) GetReceivers() []string { return strings.Split(e.Receivers, ",") } -func (e *Email) Generate() []byte { - mail := fmt.Sprintf( - "To: %s\nFrom: %s\nContent-Type: text/html;charset=utf-8\nSubject: %s\n\n%s", - e.Receivers, - e.Sender, - e.Subject, - e.Content, - ) - return []byte(mail) +func (e *Email) GetAttachments() []string { + if e.Attachments == "" { + return []string{} + } + + return strings.Split(e.Attachments, ",") +} + +func (e *Email) Generate() ([]byte, error) { + buf := bytes.NewBuffer(nil) + attachments := e.GetAttachments() + withAttachments := len(attachments) != 0 + + fmt.Fprintf(buf, "To: %s\n", strings.Join(e.GetReceivers(), ",")) + fmt.Fprintf(buf, "From: %s\n", e.Sender) + fmt.Fprintf(buf, "Subject: %s\n", e.Subject) + buf.WriteString("MIME-Version: 1.0\n") + + writer := multipart.NewWriter(buf) + boundary := writer.Boundary() + if withAttachments { + fmt.Fprintf(buf, "Content-Type: multipart/mixed; boundary=%s\n\n", boundary) + fmt.Fprintf(buf, "--%s\n", boundary) + } + + buf.WriteString("Content-Type: text/html; charset=utf-8\n\n") + buf.WriteString(e.Content) + + if withAttachments { + attachmentsData, err := getAttachmentsContent(attachments) + if err != nil { + return nil, err + } + + for filename, data := range attachmentsData { + fmt.Fprintf(buf, "\n\n--%s\n", boundary) + fmt.Fprintf(buf, "Content-Type: %s\n", http.DetectContentType(data)) + buf.WriteString("Content-Transfer-Encoding: base64\n") + fmt.Fprintf(buf, "Content-Disposition: attachment; filename=%s\n\n", filename) + + b := make([]byte, base64.StdEncoding.EncodedLen(len(data))) + base64.StdEncoding.Encode(b, data) + buf.Write(b) + fmt.Fprintf(buf, "\n\n--%s", boundary) + } + + buf.WriteString("--") + } + + return buf.Bytes(), nil } func (e *Email) Validate() error { diff --git a/services/sender.go b/services/sender.go index e1c0d82..d3f77af 100644 --- a/services/sender.go +++ b/services/sender.go @@ -33,7 +33,12 @@ func NewSender(config cfg.SMTPConfig, outboxPath string) Sender { } func (s Sender) SendMail(email *mail.Email) error { - if err := smtp.SendMail(s.smtpURL, s.auth, email.Sender, email.GetReceivers(), email.Generate()); err != nil { + content, err := email.Generate() + if err != nil { + return err + } + + if err := smtp.SendMail(s.smtpURL, s.auth, email.Sender, email.GetReceivers(), content); err != nil { return err }