feat: implemented smtp service;

feat: implemented registration emails;
fix: config variables for password length used the same env variable;
refactor: all available config variables added to docker-compose.yml
This commit is contained in:
2025-07-09 23:26:30 +03:00
parent 63b63038d1
commit 15c140db31
8 changed files with 171 additions and 21 deletions

View File

@@ -17,10 +17,21 @@
package services
import "go.uber.org/zap"
import (
"crypto/tls"
"easywish/config"
"fmt"
"net"
"net/smtp"
"strings"
"time"
"go.uber.org/zap"
errs "easywish/internal/errors"
)
type SmtpService interface {
SendEmail(to string, content string)
SendEmail(to string, subject, body string) error
}
type smtpServiceImpl struct {
@@ -31,7 +42,102 @@ func NewSmtpService(_log *zap.Logger) SmtpService {
return &smtpServiceImpl{log: _log}
}
func (s *smtpServiceImpl) SendEmail(to string, content string) {
panic("unimplemented")
}
func (s *smtpServiceImpl) SendEmail(to string, subject, body string) error {
cfg := config.GetConfig()
if !cfg.SmtpEnabled {
s.log.Error("Attempted to send an email with SMTP disabled in the config")
return errs.ErrSmtpDisabled
}
if cfg.SmtpServer == "" || cfg.SmtpPort == 0 || cfg.SmtpFrom == "" {
s.log.Error("SMTP service settings or the SMTP From paramater are not set")
return errs.ErrSmtpMissingConfiguration
}
toSlice := []string{to}
headers := map[string]string{
"From": cfg.SmtpFrom,
"To": strings.Join(toSlice, ", "),
"Subject": subject,
"MIME-Version": "1.0",
"Content-Type": "text/html; charset=UTF-8",
}
var sb strings.Builder
for k, v := range headers {
sb.WriteString(fmt.Sprintf("%s: %s\r\n", k, v))
}
sb.WriteString("\r\n" + body)
message := []byte(sb.String())
hostPort := fmt.Sprintf("%s:%d", cfg.SmtpServer, cfg.SmtpPort)
var conn net.Conn
var err error
if cfg.SmtpUseSSL {
tlsConfig := &tls.Config{ServerName: cfg.SmtpServer}
conn, err = tls.Dial("tcp", hostPort, tlsConfig)
} else {
timeout := time.Duration(cfg.SmtpTimeout) * time.Second
conn, err = net.DialTimeout("tcp", hostPort, timeout)
}
if err != nil {
s.log.Error("SMTP connection failure", zap.Error(err))
return err
}
defer conn.Close()
client, err := smtp.NewClient(conn, cfg.SmtpServer)
if err != nil {
s.log.Error("SMTP client creation failed", zap.Error(err))
return err
}
defer client.Close()
if !cfg.SmtpUseSSL && cfg.SmtpUseTLS {
tlsConfig := &tls.Config{ServerName: cfg.SmtpServer}
if err = client.StartTLS(tlsConfig); err != nil {
return err
}
}
// Authenticate if credentials exist
if cfg.SmtpUser != "" && cfg.SmtpPassword != "" {
auth := smtp.PlainAuth("", cfg.SmtpUser, cfg.SmtpPassword, cfg.SmtpServer)
if err = client.Auth(auth); err != nil {
s.log.Error("SMTP authentication failure", zap.Error(err))
return err
}
}
if err = client.Mail(cfg.SmtpFrom); err != nil {
s.log.Error("SMTP sender set failed", zap.Error(err))
return err
}
for _, recipient := range to {
if err = client.Rcpt(string(recipient)); err != nil {
s.log.Error("SMTP recipient set failed", zap.Error(err))
return err
}
}
// Send email body
w, err := client.Data()
if err != nil {
s.log.Error("SMTP data command failed", zap.Error(err))
return err
}
if _, err = w.Write(message); err != nil {
s.log.Error("SMTP message write failed", zap.Error(err))
return err
}
if err = w.Close(); err != nil {
s.log.Error("SMTP message close failed", zap.Error(err))
return err
}
return client.Quit()
}