From e545e99cc35f5ac60e6e97c21565c35970387cde Mon Sep 17 00:00:00 2001 From: Engineer Date: Mon, 20 Jul 2026 02:59:27 +0200 Subject: [PATCH 1/4] fix(sender): add connection timeouts to SMTP dial Both tls.Dial (port 465) and smtp.Dial (other ports) could block indefinitely if the remote server is slow to respond, a firewall silently drops packets, or DNS resolution is slow. This caused the entire application to hang on outgoing mail. Replace with tls.DialWithDialer and net.DialTimeout + smtp.NewClient, both using a 30-second timeout. Also switch addr formatting from fmt.Sprintf to net.JoinHostPort for correct IPv6 literal handling. --- internal/httpclient/httpclient.go | 3 +++ sender/sender.go | 19 ++++++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/internal/httpclient/httpclient.go b/internal/httpclient/httpclient.go index 55fd5321..4f970482 100644 --- a/internal/httpclient/httpclient.go +++ b/internal/httpclient/httpclient.go @@ -22,6 +22,9 @@ const ( InstallTimeout = 30 * time.Second // UpdateCheckTimeout bounds version checks and asset downloads from main (main.go). UpdateCheckTimeout = 30 * time.Second + // SMTPDialTimeout bounds SMTP TCP + TLS handshake and plain TCP dial + // used by sender/sender.go. + SMTPDialTimeout = 30 * time.Second // IMAPBatchActionTimeout bounds bulk IMAP operations (delete/archive/move) from main (main.go). IMAPBatchActionTimeout = 60 * time.Second // IMAPSearchTimeout bounds server-side IMAP search queries from main (main.go). diff --git a/sender/sender.go b/sender/sender.go index 0a1ac1d6..d5166f8f 100644 --- a/sender/sender.go +++ b/sender/sender.go @@ -2,6 +2,7 @@ package sender import ( "bytes" + "context" "crypto/rand" "crypto/tls" "crypto/x509" @@ -13,6 +14,7 @@ import ( "mime" "mime/multipart" "mime/quotedprintable" + "net" "net/mail" "net/smtp" "net/textproto" @@ -26,6 +28,7 @@ import ( "github.com/emersion/go-pgpmail" "github.com/floatpane/matcha/clib" "github.com/floatpane/matcha/config" + "github.com/floatpane/matcha/internal/httpclient" "github.com/floatpane/matcha/internal/loglevel" "github.com/floatpane/matcha/pgp" "github.com/yuin/goldmark" @@ -682,7 +685,7 @@ func SendEmail(account *config.Account, to, cc, bcc []string, subject, plainBody allRecipients = append(allRecipients, cc...) allRecipients = append(allRecipients, bcc...) - addr := fmt.Sprintf("%s:%d", smtpServer, smtpPort) + addr := net.JoinHostPort(smtpServer, fmt.Sprintf("%d", smtpPort)) tlsConfig := &tls.Config{ ServerName: smtpServer, @@ -700,7 +703,11 @@ func SendEmail(account *config.Account, to, cc, bcc []string, subject, plainBody // Port 465 uses implicit TLS (the connection starts with TLS). // All other ports use plain TCP with optional STARTTLS upgrade. if smtpPort == 465 { - conn, err := tls.Dial("tcp", addr, tlsConfig) //nolint:noctx + dialer := tls.Dialer{ + NetDialer: &net.Dialer{Timeout: httpclient.SMTPDialTimeout}, + Config: tlsConfig, + } + conn, err := dialer.DialContext(context.Background(), "tcp", addr) if err != nil { return nil, err } @@ -711,8 +718,14 @@ func SendEmail(account *config.Account, to, cc, bcc []string, subject, plainBody } } else { var err error - c, err = smtp.Dial(addr) + nd := &net.Dialer{Timeout: httpclient.SMTPDialTimeout} + conn, dialErr := nd.DialContext(context.Background(), "tcp", addr) + if dialErr != nil { + return nil, dialErr + } + c, err = smtp.NewClient(conn, smtpServer) if err != nil { + conn.Close() //nolint:errcheck,gosec return nil, err } } From ba1eaa5993d28700eaa45f5ca74426f12f171d1c Mon Sep 17 00:00:00 2001 From: kawacukennedy Date: Mon, 20 Jul 2026 09:43:16 +0200 Subject: [PATCH 2/4] ci: retrigger CI From a40ff9c0941cb008bb8a3c92f521db7b779dab56 Mon Sep 17 00:00:00 2001 From: kawacukennedy Date: Mon, 20 Jul 2026 09:43:17 +0200 Subject: [PATCH 3/4] ci: retrigger CI From e6ff9acf6aa3869c9cf8672869489d5d0d3eaf6a Mon Sep 17 00:00:00 2001 From: kawacukennedy Date: Mon, 20 Jul 2026 09:43:18 +0200 Subject: [PATCH 4/4] ci: retrigger CI