164 lines
4.2 KiB
Go
164 lines
4.2 KiB
Go
|
|
package mail
|
||
|
|
|
||
|
|
import (
|
||
|
|
"crypto/tls"
|
||
|
|
"fmt"
|
||
|
|
"net/smtp"
|
||
|
|
|
||
|
|
"bamort/config"
|
||
|
|
"bamort/logger"
|
||
|
|
)
|
||
|
|
|
||
|
|
// Client represents an SMTP mail client
|
||
|
|
type Client struct {
|
||
|
|
host string
|
||
|
|
port int
|
||
|
|
username string
|
||
|
|
password string
|
||
|
|
from string
|
||
|
|
}
|
||
|
|
|
||
|
|
// Message represents an email message
|
||
|
|
type Message struct {
|
||
|
|
To string
|
||
|
|
Subject string
|
||
|
|
Body string
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewClient creates a new SMTP mail client from config
|
||
|
|
func NewClient() *Client {
|
||
|
|
cfg := config.Cfg
|
||
|
|
return &Client{
|
||
|
|
host: cfg.MailHost,
|
||
|
|
port: cfg.MailPort,
|
||
|
|
username: cfg.MailUsername,
|
||
|
|
password: cfg.MailPassword,
|
||
|
|
from: cfg.MailFrom,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Send sends an email message via SMTP
|
||
|
|
func (c *Client) Send(msg Message) error {
|
||
|
|
if c.host == "" {
|
||
|
|
logger.Warn("SMTP Host nicht konfiguriert - E-Mail-Versand übersprungen")
|
||
|
|
return fmt.Errorf("SMTP host not configured")
|
||
|
|
}
|
||
|
|
|
||
|
|
logger.Debug("Sende E-Mail an %s via SMTP %s:%d", msg.To, c.host, c.port)
|
||
|
|
|
||
|
|
// Prepare email headers and body
|
||
|
|
headers := make(map[string]string)
|
||
|
|
headers["From"] = c.from
|
||
|
|
headers["To"] = msg.To
|
||
|
|
headers["Subject"] = msg.Subject
|
||
|
|
headers["MIME-Version"] = "1.0"
|
||
|
|
headers["Content-Type"] = "text/html; charset=\"UTF-8\""
|
||
|
|
|
||
|
|
// Build message
|
||
|
|
message := ""
|
||
|
|
for key, value := range headers {
|
||
|
|
message += fmt.Sprintf("%s: %s\r\n", key, value)
|
||
|
|
}
|
||
|
|
message += "\r\n" + msg.Body
|
||
|
|
|
||
|
|
// Connect to SMTP server
|
||
|
|
serverAddr := fmt.Sprintf("%s:%d", c.host, c.port)
|
||
|
|
|
||
|
|
// Create TLS config
|
||
|
|
tlsConfig := &tls.Config{
|
||
|
|
ServerName: c.host,
|
||
|
|
InsecureSkipVerify: false,
|
||
|
|
}
|
||
|
|
|
||
|
|
var err error
|
||
|
|
var client *smtp.Client
|
||
|
|
|
||
|
|
// Port 465 requires direct TLS connection, port 587 uses STARTTLS
|
||
|
|
if c.port == 465 {
|
||
|
|
// Direct TLS connection (implicit SSL)
|
||
|
|
conn, err := tls.Dial("tcp", serverAddr, tlsConfig)
|
||
|
|
if err != nil {
|
||
|
|
logger.Error("Fehler beim Verbinden mit SMTP-Server (TLS): %s", err.Error())
|
||
|
|
return fmt.Errorf("failed to connect to SMTP server: %w", err)
|
||
|
|
}
|
||
|
|
defer conn.Close()
|
||
|
|
|
||
|
|
client, err = smtp.NewClient(conn, c.host)
|
||
|
|
if err != nil {
|
||
|
|
logger.Error("Fehler beim Erstellen des SMTP-Clients: %s", err.Error())
|
||
|
|
return fmt.Errorf("failed to create SMTP client: %w", err)
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
// Port 587 or other - use STARTTLS
|
||
|
|
client, err = smtp.Dial(serverAddr)
|
||
|
|
if err != nil {
|
||
|
|
logger.Error("Fehler beim Verbinden mit SMTP-Server: %s", err.Error())
|
||
|
|
return fmt.Errorf("failed to dial SMTP server: %w", err)
|
||
|
|
}
|
||
|
|
defer client.Close()
|
||
|
|
|
||
|
|
// Start TLS if available
|
||
|
|
if ok, _ := client.Extension("STARTTLS"); ok {
|
||
|
|
if err = client.StartTLS(tlsConfig); err != nil {
|
||
|
|
logger.Error("Fehler beim STARTTLS: %s", err.Error())
|
||
|
|
return fmt.Errorf("failed to start TLS: %w", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Authenticate
|
||
|
|
if c.username != "" && c.password != "" {
|
||
|
|
auth := smtp.PlainAuth("", c.username, c.password, c.host)
|
||
|
|
if err = client.Auth(auth); err != nil {
|
||
|
|
logger.Error("Fehler bei der SMTP-Authentifizierung: %s", err.Error())
|
||
|
|
return fmt.Errorf("failed to authenticate: %w", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Set sender
|
||
|
|
if err = client.Mail(c.from); err != nil {
|
||
|
|
logger.Error("Fehler beim Setzen des Absenders: %s", err.Error())
|
||
|
|
return fmt.Errorf("failed to set sender: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Set recipient
|
||
|
|
if err = client.Rcpt(msg.To); err != nil {
|
||
|
|
logger.Error("Fehler beim Setzen des Empfängers: %s", err.Error())
|
||
|
|
return fmt.Errorf("failed to set recipient: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Send message body
|
||
|
|
writer, err := client.Data()
|
||
|
|
if err != nil {
|
||
|
|
logger.Error("Fehler beim Öffnen des Data-Writers: %s", err.Error())
|
||
|
|
return fmt.Errorf("failed to open data writer: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
_, err = writer.Write([]byte(message))
|
||
|
|
if err != nil {
|
||
|
|
logger.Error("Fehler beim Schreiben der Nachricht: %s", err.Error())
|
||
|
|
writer.Close()
|
||
|
|
return fmt.Errorf("failed to write message: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
err = writer.Close()
|
||
|
|
if err != nil {
|
||
|
|
logger.Error("Fehler beim Schließen des Data-Writers: %s", err.Error())
|
||
|
|
return fmt.Errorf("failed to close data writer: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Quit
|
||
|
|
if err = client.Quit(); err != nil {
|
||
|
|
logger.Error("Fehler beim Beenden der SMTP-Verbindung: %s", err.Error())
|
||
|
|
return fmt.Errorf("failed to quit SMTP connection: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
logger.Info("E-Mail erfolgreich an %s versendet", msg.To)
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// IsConfigured returns true if the mail client is properly configured
|
||
|
|
func (c *Client) IsConfigured() bool {
|
||
|
|
return c.host != "" && c.port > 0 && c.from != ""
|
||
|
|
}
|