first commit
This commit is contained in:
94
internal/mail/mail.go
Normal file
94
internal/mail/mail.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/smtp"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Config holds outbound mail settings read from environment variables.
|
||||
type Config struct {
|
||||
Host string // SMTP_HOST
|
||||
Port string // SMTP_PORT
|
||||
User string // SMTP_USER
|
||||
Pass string // SMTP_PASS
|
||||
From string // SMTP_FROM
|
||||
}
|
||||
|
||||
// ConfigFromEnv reads SMTP settings from environment variables.
|
||||
func ConfigFromEnv() Config {
|
||||
port := os.Getenv("SMTP_PORT")
|
||||
if port == "" {
|
||||
port = "587"
|
||||
}
|
||||
return Config{
|
||||
Host: os.Getenv("SMTP_HOST"),
|
||||
Port: port,
|
||||
User: os.Getenv("SMTP_USER"),
|
||||
Pass: os.Getenv("SMTP_PASS"),
|
||||
From: os.Getenv("SMTP_FROM"),
|
||||
}
|
||||
}
|
||||
|
||||
// Ready reports whether all required SMTP fields are set.
|
||||
func (c Config) Ready() bool {
|
||||
return c.Host != "" && c.User != "" && c.Pass != "" && c.From != ""
|
||||
}
|
||||
|
||||
// sendEmail sends a plain-text email to a single recipient.
|
||||
// It tries implicit TLS first, then falls back to STARTTLS via smtp.SendMail.
|
||||
func sendEmail(cfg Config, to, subject, body string) error {
|
||||
addr := cfg.Host + ":" + cfg.Port
|
||||
auth := smtp.PlainAuth("", cfg.User, cfg.Pass, cfg.Host)
|
||||
|
||||
msg := fmt.Sprintf(
|
||||
"From: %s\r\nTo: %s\r\nSubject: %s\r\nMIME-Version: 1.0\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\n%s",
|
||||
cfg.From, to, subject, body,
|
||||
)
|
||||
|
||||
conn, err := tls.Dial("tcp", addr, &tls.Config{ServerName: cfg.Host})
|
||||
if err != nil {
|
||||
return smtp.SendMail(addr, auth, cfg.From, []string{to}, []byte(msg))
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client, err := smtp.NewClient(conn, cfg.Host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
if err = client.Auth(auth); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = client.Mail(cfg.From); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = client.Rcpt(to); err != nil {
|
||||
return err
|
||||
}
|
||||
wc, err := client.Data()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = fmt.Fprint(wc, msg); err != nil {
|
||||
return err
|
||||
}
|
||||
return wc.Close()
|
||||
}
|
||||
|
||||
// SendPasswordReset sends a password-reset email containing resetURL.
|
||||
func SendPasswordReset(cfg Config, toEmail, resetURL string) error {
|
||||
subject := "Reset your Arcline IT password"
|
||||
body := fmt.Sprintf(
|
||||
"Hello,\r\n\r\n"+
|
||||
"A password reset was requested for your Arcline IT account.\r\n\r\n"+
|
||||
"Click the link below to set a new password (valid for 1 hour):\r\n\r\n"+
|
||||
"%s\r\n\r\n"+
|
||||
"If you did not request this, you can safely ignore this email.\r\n\r\n"+
|
||||
"-- Arcline IT Support\r\n",
|
||||
resetURL,
|
||||
)
|
||||
return sendEmail(cfg, toEmail, subject, body)
|
||||
}
|
||||
Reference in New Issue
Block a user