fatto ordine
This commit is contained in:
136
smtp.go
Normal file
136
smtp.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/smtp"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type smtpConfig struct {
|
||||
Host string
|
||||
Port int
|
||||
User string
|
||||
Password string
|
||||
}
|
||||
|
||||
func getSMTPConfigFromEnv() smtpConfig {
|
||||
port := 587
|
||||
if rawPort := strings.TrimSpace(os.Getenv("STARTTLS_PORT")); rawPort != "" {
|
||||
if p, err := strconv.Atoi(rawPort); err == nil {
|
||||
port = p
|
||||
}
|
||||
}
|
||||
return smtpConfig{
|
||||
Host: strings.TrimSpace(os.Getenv("SMTP")),
|
||||
Port: port,
|
||||
User: strings.TrimSpace(os.Getenv("SMTP_USER")),
|
||||
Password: strings.TrimSpace(os.Getenv("SMTP_PASSWORD")),
|
||||
}
|
||||
}
|
||||
|
||||
func (s smtpConfig) isConfigured() bool {
|
||||
return s.Host != "" && s.User != "" && s.Password != "" && s.Port > 0
|
||||
}
|
||||
|
||||
func getAppBaseURL() string {
|
||||
base := strings.TrimSpace(os.Getenv("SITE_URL"))
|
||||
if base == "" {
|
||||
base = strings.TrimSpace(os.Getenv("APP_BASE_URL"))
|
||||
}
|
||||
if base == "" {
|
||||
return "http://localhost:6081"
|
||||
}
|
||||
return strings.TrimRight(base, "/")
|
||||
}
|
||||
|
||||
func sendPasswordResetEmail(config smtpConfig, recipientEmail, resetURL string) error {
|
||||
body, err := renderTransactionalTemplate("password_reset.html", map[string]string{
|
||||
"ActionURL": resetURL,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sendHTMLEmail(config, recipientEmail, "Bag Exchange - Password reset", body)
|
||||
}
|
||||
|
||||
func sendEmailVerificationEmail(config smtpConfig, recipientEmail, verifyURL string) error {
|
||||
body, err := renderTransactionalTemplate("verify_email.html", map[string]string{
|
||||
"ActionURL": verifyURL,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sendHTMLEmail(config, recipientEmail, "Bag Exchange - Verify your email", body)
|
||||
}
|
||||
|
||||
func sendHTMLEmail(config smtpConfig, recipientEmail, subject, htmlBody string) error {
|
||||
if isDevEmailMode() {
|
||||
return saveDevEmail(recipientEmail, subject, htmlBody)
|
||||
}
|
||||
|
||||
auth := smtp.PlainAuth("", config.User, config.Password, config.Host)
|
||||
message := "From: " + config.User + "\r\n" +
|
||||
"To: " + recipientEmail + "\r\n" +
|
||||
"Subject: " + subject + "\r\n" +
|
||||
"MIME-Version: 1.0\r\n" +
|
||||
"Content-Type: text/html; charset=UTF-8\r\n\r\n" +
|
||||
htmlBody
|
||||
return smtp.SendMail(fmt.Sprintf("%s:%d", config.Host, config.Port), auth, config.User, []string{recipientEmail}, []byte(message))
|
||||
}
|
||||
|
||||
func renderTransactionalTemplate(templateName string, data any) (string, error) {
|
||||
tmpl, err := template.ParseFiles(filepath.Join("templates", "transactionalMails", templateName))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var out bytes.Buffer
|
||||
if err := tmpl.Execute(&out, data); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return out.String(), nil
|
||||
}
|
||||
|
||||
func isDevEmailMode() bool {
|
||||
return strings.EqualFold(strings.TrimSpace(os.Getenv("EMAIL_MODE")), "dev")
|
||||
}
|
||||
|
||||
func saveDevEmail(recipientEmail, subject, htmlBody string) error {
|
||||
const dir = "devEmails"
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filename := fmt.Sprintf("%s_%s.html", time.Now().UTC().Format("20060102_150405"), sanitizeFilename(subject))
|
||||
path := filepath.Join(dir, filename)
|
||||
|
||||
payload := "<!--\n" +
|
||||
"To: " + recipientEmail + "\n" +
|
||||
"Subject: " + subject + "\n" +
|
||||
"GeneratedAtUTC: " + time.Now().UTC().Format(time.RFC3339) + "\n" +
|
||||
"-->\n" + htmlBody
|
||||
|
||||
return os.WriteFile(path, []byte(payload), 0o644)
|
||||
}
|
||||
|
||||
func sanitizeFilename(value string) string {
|
||||
var b strings.Builder
|
||||
for _, r := range strings.ToLower(strings.TrimSpace(value)) {
|
||||
switch {
|
||||
case (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9'):
|
||||
b.WriteRune(r)
|
||||
case r == ' ' || r == '-' || r == '_':
|
||||
b.WriteRune('_')
|
||||
}
|
||||
}
|
||||
name := strings.Trim(b.String(), "_")
|
||||
if name == "" {
|
||||
return "email"
|
||||
}
|
||||
return name
|
||||
}
|
||||
Reference in New Issue
Block a user