From 21e6ff3e9e5be6d99984e84f8cdacaa07a5dadfc Mon Sep 17 00:00:00 2001 From: Joe Julian Date: Sat, 28 Mar 2026 16:35:34 -0700 Subject: [PATCH] Fix SMTP submission HELO handling --- README.md | 1 + main.go | 30 ++++++++++++++++++++++++++++++ main_test.go | 9 +++++++++ 3 files changed, 40 insertions(+) diff --git a/README.md b/README.md index b75d09a..aeb0ea3 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Recommended settings: - `ALMA_PASSWORD` - `SMTP_PORT=587` - `SMTP_STARTTLS=true` +- `SMTP_HELO_NAME=mailer.example.invalid` - `SMTP_USERNAME` - `SMTP_PASSWORD` - `PRINT_REPORT=true` diff --git a/main.go b/main.go index f6c0de2..dc08098 100644 --- a/main.go +++ b/main.go @@ -39,6 +39,7 @@ type Config struct { SMTPUsername string SMTPPassword string SMTPStartTLS bool + SMTPHeloName string EmailFrom string EmailTo []string PrintReport bool @@ -172,6 +173,10 @@ func loadConfig() (Config, error) { return Config{}, err } smtpStartTLS := readBool("SMTP_STARTTLS", true) + smtpHeloName, err := readValue("SMTP_HELO_NAME", "", false) + if err != nil { + return Config{}, err + } emailFrom, err := readValue("EMAIL_FROM", "", false) if err != nil { return Config{}, err @@ -204,6 +209,7 @@ func loadConfig() (Config, error) { SMTPUsername: smtpUsername, SMTPPassword: smtpPassword, SMTPStartTLS: smtpStartTLS, + SMTPHeloName: normalizeSMTPHeloName(smtpHeloName), EmailFrom: emailFrom, EmailTo: emailTo, PrintReport: printReport, @@ -594,11 +600,18 @@ func sendEmail(cfg Config, subject, body string) error { defer conn.Close() host := cfg.SMTPHost + heloName := normalizeSMTPHeloName(cfg.SMTPHeloName) + if err := conn.Hello(heloName); err != nil { + return err + } if cfg.SMTPStartTLS { if ok, _ := conn.Extension("STARTTLS"); ok { if err := conn.StartTLS(&tls.Config{ServerName: host, MinVersion: tls.VersionTLS12}); err != nil { return err } + if err := conn.Hello(heloName); err != nil { + return err + } } } if cfg.SMTPUsername != "" { @@ -641,6 +654,23 @@ func sendEmail(cfg Config, subject, body string) error { return conn.Quit() } +func normalizeSMTPHeloName(value string) string { + value = strings.TrimSpace(value) + if value == "" { + hostname, err := os.Hostname() + if err == nil { + value = strings.TrimSpace(hostname) + } + } + if value == "" { + return "localhost.localdomain" + } + if strings.Contains(value, ".") { + return value + } + return value + ".localdomain" +} + func inferAssignmentDate(monthDay string, now time.Time) time.Time { return inferDate(monthDay, now, false) } diff --git a/main_test.go b/main_test.go index 9f3589c..50088c2 100644 --- a/main_test.go +++ b/main_test.go @@ -132,6 +132,15 @@ func TestLoadAlmaCredsFromFile(t *testing.T) { } } +func TestNormalizeSMTPHeloName(t *testing.T) { + if got := normalizeSMTPHeloName("mail.example.invalid"); got != "mail.example.invalid" { + t.Fatalf("got %q", got) + } + if got := normalizeSMTPHeloName("mailer"); got != "mailer.localdomain" { + t.Fatalf("got %q", got) + } +} + func mustParseHTML(text string) *html.Node { doc, err := html.Parse(strings.NewReader(text)) if err != nil {