103 lines
2.2 KiB
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")
|
|
}
|