From 8fac2dbb9904463d735f21afb6d422959f0c301f Mon Sep 17 00:00:00 2001 From: hungtcs Date: Wed, 27 May 2026 11:31:05 +0800 Subject: [PATCH] feat(email): add support for implicit TLS (SMTPS) - Add EMAIL_SMTP_ENABLE_IMPLICIT_TLS config variable to replace hardcoded port check - Send function now uses the config flag to establish TLS before any SMTP command - Skip STARTTLS when implicit TLS is active --- app/pkg/env/env.go | 11 ++++++----- app/services/email/smtp/smtp.go | 29 ++++++++++++++++++++++------ app/services/email/smtp/smtp_test.go | 2 +- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/app/pkg/env/env.go b/app/pkg/env/env.go index fdf91b81c..8ebbe427e 100644 --- a/app/pkg/env/env.go +++ b/app/pkg/env/env.go @@ -117,11 +117,12 @@ type config struct { Region string `env:"EMAIL_MAILGUN_REGION,default=US"` // possible values: US or EU } SMTP struct { - Host string `env:"EMAIL_SMTP_HOST"` - Port string `env:"EMAIL_SMTP_PORT"` - Username string `env:"EMAIL_SMTP_USERNAME"` - Password string `env:"EMAIL_SMTP_PASSWORD"` - EnableStartTLS bool `env:"EMAIL_SMTP_ENABLE_STARTTLS,default=true"` + Host string `env:"EMAIL_SMTP_HOST"` + Port string `env:"EMAIL_SMTP_PORT"` + Username string `env:"EMAIL_SMTP_USERNAME"` + Password string `env:"EMAIL_SMTP_PASSWORD"` + EnableStartTLS bool `env:"EMAIL_SMTP_ENABLE_STARTTLS,default=true"` + EnableImplicitTLS bool `env:"EMAIL_SMTP_ENABLE_IMPLICIT_TLS,default=false"` } } BlobStorage struct { diff --git a/app/services/email/smtp/smtp.go b/app/services/email/smtp/smtp.go index a5b8f30a7..962524c9c 100644 --- a/app/services/email/smtp/smtp.go +++ b/app/services/email/smtp/smtp.go @@ -100,7 +100,7 @@ func sendMail(ctx context.Context, c *cmd.SendMail) { smtpConfig := env.Config.Email.SMTP servername := fmt.Sprintf("%s:%s", smtpConfig.Host, smtpConfig.Port) auth := authenticate(smtpConfig.Username, smtpConfig.Password, smtpConfig.Host) - err = Send(localname, servername, smtpConfig.EnableStartTLS, auth, email.NoReply, []string{to.Address}, b.Bytes()) + err = Send(localname, servername, smtpConfig.EnableStartTLS, smtpConfig.EnableImplicitTLS, auth, email.NoReply, []string{to.Address}, b.Bytes()) if err != nil { panic(errors.Wrap(err, "failed to send email with template %s", c.TemplateName)) } @@ -108,17 +108,34 @@ func sendMail(ctx context.Context, c *cmd.SendMail) { } } -var Send = func(localName, serverAddress string, enableStartTLS bool, a gosmtp.Auth, from string, to []string, msg []byte) error { +var Send = func(localName, serverAddress string, enableStartTLS, enableImplicitTLS bool, a gosmtp.Auth, from string, to []string, msg []byte) error { host, _, _ := net.SplitHostPort(serverAddress) - c, err := gosmtp.Dial(serverAddress) - if err != nil { - return err + + var c *gosmtp.Client + var err error + + if enableImplicitTLS { + // Implicit TLS (SMTPS): wrap connection in TLS before any SMTP command. + // Typically used on port 465. + conn, err := tls.Dial("tcp", serverAddress, &tls.Config{ServerName: host}) + if err != nil { + return err + } + c, err = gosmtp.NewClient(conn, host) + if err != nil { + return err + } + } else { + c, err = gosmtp.Dial(serverAddress) + if err != nil { + return err + } } defer func() { _ = c.Close() }() if err = c.Hello(localName); err != nil { return err } - if enableStartTLS { + if enableStartTLS && !enableImplicitTLS { if ok, _ := c.Extension("STARTTLS"); ok { config := &tls.Config{ServerName: host} if err = c.StartTLS(config); err != nil { diff --git a/app/services/email/smtp/smtp_test.go b/app/services/email/smtp/smtp_test.go index 05ac8412c..78cec2934 100644 --- a/app/services/email/smtp/smtp_test.go +++ b/app/services/email/smtp/smtp_test.go @@ -29,7 +29,7 @@ var ctx context.Context var requests = make([]request, 0) -func mockSend(localname, servername string, enableStartTLS bool, auth gosmtp.Auth, from string, to []string, body []byte) error { +func mockSend(localname, servername string, enableStartTLS, enableImplicitTLS bool, auth gosmtp.Auth, from string, to []string, body []byte) error { requests = append(requests, request{servername, auth, from, to, body}) return nil }