package email import ( "bytes" "crypto/tls" "encoding/base64" "fmt" "io" "net/smtp" "strings" ) const ( mime = "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n" subjectReport = "B2 vs Local Files" templateSuccess = "success.html" templateConfirm = "confirm.html" templateForgotPassword = "forgot_password.html" templateRegister = "register.html" delimeter = "**=myohmy689407924327" ) type EmailService struct { auth smtp.Auth host string port string from string tlsconfig *tls.Config } type EmailWithAttachments struct { To string Bucket string BackupDir string Count map[string]int Attachments []EmailAttachment } type EmailAttachment struct { File io.Reader Title string } func (e EmailAttachment) ReadContent() ([]byte, error) { bts, err := io.ReadAll(e.File) if err != nil { return nil, fmt.Errorf("error loading attachment: %s", err) } return bts, nil } type MailServiceConfig struct { Auth smtp.Auth Host string Port string From string // Sender email address } func NewMailService(config MailServiceConfig) *EmailService { return &EmailService{ auth: config.Auth, host: config.Host, port: config.Port, from: config.From, tlsconfig: &tls.Config{ InsecureSkipVerify: true, ServerName: config.Host, }, } } func (e *EmailService) SendOK(emailData EmailWithAttachments) error { template := strings.Replace(htmlTemplate, "{{bucket}}", emailData.Bucket, -1) template = strings.Replace(template, "{{local_backup_path}}", emailData.BackupDir, -1) for k, v := range emailData.Count { template = strings.Replace(template, "{{"+k+"}}", fmt.Sprint(v), -1) } msg, err := newMessage(e.from, emailData.To, subjectReport). withAttachments(template, emailData.Attachments) if err != nil { return fmt.Errorf("error while upload attachments %w", err) } return e.send(emailData.To, msg) } func (e *EmailService) send(to string, msg []byte) error { c, err := smtp.Dial(e.host + ":" + e.port) if err != nil { return fmt.Errorf("DIAL: %s", err) } if err = c.StartTLS(e.tlsconfig); err != nil { return fmt.Errorf("c.StartTLS: %s", err) } // Auth if err = c.Auth(e.auth); err != nil { return fmt.Errorf("c.Auth: %s", err) } // To && From if err = c.Mail(e.from); err != nil { return fmt.Errorf("c.Mail: %s", err) } if err = c.Rcpt(to); err != nil { return fmt.Errorf("c.Rcpt: %s", err) } // Data w, err := c.Data() if err != nil { return fmt.Errorf("c.Data: %s", err) } written, err := w.Write(msg) if err != nil { return fmt.Errorf("w.Write: %s", err) } if written <= 0 { return fmt.Errorf("%d bytes written", written) } if err = w.Close(); err != nil { return fmt.Errorf("w.Close: %s", err) } if err = c.Quit(); err != nil { return fmt.Errorf("w.Quit: %s", err) } return nil } type message struct { from string to string subject string } func newMessage(from, to, subject string) message { return message{from: from, to: to, subject: subject} } func (m message) withAttachments(body string, attachments []EmailAttachment) ([]byte, error) { headers := make(map[string]string) headers["From"] = m.from headers["To"] = m.to headers["Subject"] = m.subject headers["MIME-Version"] = "1.0" var message bytes.Buffer for k, v := range headers { message.WriteString(k) message.WriteString(": ") message.WriteString(v) message.WriteString("\r\n") } message.WriteString("Content-Type: " + fmt.Sprintf("multipart/mixed; boundary=\"%s\"\r\n", delimeter)) message.WriteString("--" + delimeter + "\r\n") message.WriteString("Content-Type: text/html; charset=\"UTF-8\"\r\n\r\n") message.WriteString(body + "\r\n\r\n") for _, attachment := range attachments { attachmentRawFile, err := attachment.ReadContent() if err != nil { return nil, err } message.WriteString("--" + delimeter + "\r\n") message.WriteString("Content-Disposition: attachment; filename=\"" + attachment.Title + "\"\r\n") message.WriteString("Content-Type: application/octet-stream\r\n") message.WriteString("Content-Transfer-Encoding: base64\r\n\r\n") message.WriteString(base64.StdEncoding.EncodeToString(attachmentRawFile) + "\r\n") } message.WriteString("--" + delimeter + "--") // End the message return message.Bytes(), nil }