132 lines
3.0 KiB
Go
132 lines
3.0 KiB
Go
// Copyright (c) 2025 Nikolai Papin
|
|
//
|
|
// This file is part of Easywish
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
|
// the GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package services
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"easywish/config"
|
|
"fmt"
|
|
"net"
|
|
"net/smtp"
|
|
"strings"
|
|
"time"
|
|
|
|
errs "easywish/internal/errors"
|
|
)
|
|
|
|
type SmtpService interface {
|
|
SendEmail(to string, subject, body string) error
|
|
}
|
|
|
|
type smtpServiceImpl struct {
|
|
}
|
|
|
|
func NewSmtpService() SmtpService {
|
|
return &smtpServiceImpl{}
|
|
}
|
|
|
|
func (s *smtpServiceImpl) SendEmail(to string, subject, body string) error {
|
|
cfg := config.GetConfig()
|
|
|
|
if !cfg.SmtpEnabled {
|
|
return errs.ErrSmtpDisabled
|
|
}
|
|
|
|
if cfg.SmtpServer == "" || cfg.SmtpPort == 0 || cfg.SmtpFrom == "" {
|
|
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 := net.JoinHostPort(cfg.SmtpServer, fmt.Sprintf("%d", 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 {
|
|
return err
|
|
}
|
|
defer conn.Close()
|
|
|
|
client, err := smtp.NewClient(conn, cfg.SmtpServer)
|
|
if err != nil {
|
|
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 {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err = client.Mail(cfg.SmtpFrom); err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, recipient := range toSlice {
|
|
if err = client.Rcpt(recipient); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Send email body
|
|
w, err := client.Data()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err = w.Write(message); err != nil {
|
|
return err
|
|
}
|
|
if err = w.Close(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return client.Quit()
|
|
}
|