From a9f0739b52d52c6ca9cf08f45dfaa282e71415f5 Mon Sep 17 00:00:00 2001 From: Arnaud Dezandee <4415204+arnaud-dezandee@users.noreply.github.com> Date: Thu, 4 Jun 2026 08:07:47 +0200 Subject: [PATCH] fix(email): set EHLO hostname for SMTP relay compatibility (#3679) --- server/internal/service/email.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/server/internal/service/email.go b/server/internal/service/email.go index d7270d17d..ea2f83433 100644 --- a/server/internal/service/email.go +++ b/server/internal/service/email.go @@ -31,6 +31,7 @@ type EmailService struct { smtpPassword string smtpTLSInsecure bool smtpTLSImplicit bool + smtpEHLOName string } func NewEmailService() *EmailService { @@ -49,6 +50,14 @@ func NewEmailService() *EmailService { smtpPassword := os.Getenv("SMTP_PASSWORD") smtpTLSInsecure := os.Getenv("SMTP_TLS_INSECURE") == "true" + // EHLO/HELO name. net/smtp defaults to "localhost", which strict relays + // (e.g. smtp-relay.gmail.com) reject from a public source. Fall back to the + // machine hostname when SMTP_EHLO_NAME is unset. + smtpEHLOName := strings.TrimSpace(os.Getenv("SMTP_EHLO_NAME")) + if smtpEHLOName == "" { + smtpEHLOName, _ = os.Hostname() + } + // SMTP_TLS=implicit forces an immediate TLS handshake on connect (SMTPS). // Required by providers like Aliyun enterprise mail that only offer port 465 // SSL and do not advertise STARTTLS. Default (empty / "starttls") preserves @@ -89,6 +98,7 @@ func NewEmailService() *EmailService { smtpPassword: smtpPassword, smtpTLSInsecure: smtpTLSInsecure, smtpTLSImplicit: smtpTLSImplicit, + smtpEHLOName: smtpEHLOName, } } @@ -129,6 +139,15 @@ func (s *EmailService) sendSMTP(to, subject, htmlBody string) error { } defer c.Close() + // Greet with a real hostname before any other command, else net/smtp lazily + // EHLOs "localhost" — which strict relays drop, surfacing as an opaque EOF on + // a later command rather than at the EHLO itself. + if s.smtpEHLOName != "" { + if err = c.Hello(s.smtpEHLOName); err != nil { + return fmt.Errorf("smtp EHLO %s: %w", s.smtpEHLOName, err) + } + } + // STARTTLS upgrade only makes sense when the underlying connection is still // plaintext. Skip when we already dialed with implicit TLS. if !s.smtpTLSImplicit {