backend-server-v2/internal/mailer/smtp.go

103 lines
2.2 KiB
Go

package mailer
import (
"context"
"fmt"
"net"
"net/mail"
"net/smtp"
"strconv"
"strings"
"time"
"trustcontact/internal/config"
)
type SMTPMailer struct {
host string
port int
username string
password string
from string
fromName string
}
func NewSMTPMailer(cfg *config.Config) (*SMTPMailer, error) {
if strings.TrimSpace(cfg.SMTP.Host) == "" {
return nil, fmt.Errorf("smtp host is required")
}
if cfg.SMTP.Port <= 0 {
return nil, fmt.Errorf("smtp port must be > 0")
}
if strings.TrimSpace(cfg.SMTP.From) == "" {
return nil, fmt.Errorf("smtp from is required")
}
return &SMTPMailer{
host: cfg.SMTP.Host,
port: cfg.SMTP.Port,
username: cfg.SMTP.Username,
password: cfg.SMTP.Password,
from: cfg.SMTP.From,
fromName: cfg.SMTP.FromName,
}, nil
}
func (m *SMTPMailer) Send(ctx context.Context, to, subject, htmlBody, textBody string) error {
if ctx != nil {
if err := ctx.Err(); err != nil {
return err
}
}
addr := net.JoinHostPort(m.host, strconv.Itoa(m.port))
msg := m.buildMessage(to, subject, htmlBody, textBody)
var auth smtp.Auth
if m.username != "" {
auth = smtp.PlainAuth("", m.username, m.password, m.host)
}
if err := smtp.SendMail(addr, auth, m.from, []string{to}, []byte(msg)); err != nil {
return err
}
if ctx != nil {
if err := ctx.Err(); err != nil {
return err
}
}
return nil
}
func (m *SMTPMailer) buildMessage(to, subject, htmlBody, textBody string) string {
boundary := fmt.Sprintf("mixed-%d", time.Now().UTC().UnixNano())
fromAddr := (&mail.Address{Name: m.fromName, Address: m.from}).String()
toAddr := (&mail.Address{Address: to}).String()
headers := []string{
fmt.Sprintf("From: %s", fromAddr),
fmt.Sprintf("To: %s", toAddr),
fmt.Sprintf("Subject: %s", subject),
"MIME-Version: 1.0",
fmt.Sprintf("Content-Type: multipart/alternative; boundary=%q", boundary),
"",
}
parts := []string{
fmt.Sprintf("--%s", boundary),
"Content-Type: text/plain; charset=UTF-8",
"",
textBody,
fmt.Sprintf("--%s", boundary),
"Content-Type: text/html; charset=UTF-8",
"",
htmlBody,
fmt.Sprintf("--%s--", boundary),
"",
}
return strings.Join(append(headers, parts...), "\r\n")
}