package mail import ( "bytes" "encoding/base64" "encoding/json" "fmt" "mime/multipart" "net/http" "os" "path/filepath" "strings" "github.com/go-playground/validator/v10" ) 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"` Attachments string `json:"attachments,omitempty"` } func FromJSON(path string) (Email, error) { var email Email content, err := os.ReadFile(path) if err != nil { return email, fmt.Errorf("%w, unable to read the file: %s", err, path) } if err := json.Unmarshal(content, &email); err != nil { return email, err } if err := email.Validate(); err != nil { return email, err } return email, nil } func (e *Email) GetReceivers() []string { return strings.Split(e.Receivers, ",") } 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 { validate = validator.New(validator.WithRequiredStructEnabled()) if err := validate.Struct(e); err != nil { return err } return nil }