{"record":{"id":"21983108ba1b8202","repo":"Billionmail/BillionMail","slug":"failed-to-connect-w","errorCode":null,"errorMessage":"failed to connect: %w","messagePattern":"failed to connect: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/mail_service/sending.go","lineNumber":320,"sourceCode":"\n\treturn fmt.Sprintf(\"<%d.%s@%s>\", timestampMillis, randomID, domainPart)\n}\n\n// Send sends an email to specified recipients\nfunc (e *EmailSender) Send(message Message, recipients []string) error {\n\tif len(recipients) == 0 {\n\t\treturn fmt.Errorf(\"no recipients specified\")\n\t}\n\n\te.mutex.Lock()\n\tdefer e.mutex.Unlock()\n\n\t// Make sure we have a connection\n\tif !e.connected || e.client == nil {\n\t\te.mutex.Unlock()\n\t\tif err := e.Connect(); err != nil {\n\t\t\te.mutex.Lock()\n\t\t\treturn fmt.Errorf(\"failed to connect: %w\", err)\n\t\t}\n\t\te.mutex.Lock()\n\t}\n\n\t// Try to send the message, with reconnect on failure\n\terr := e.doSend(message, recipients)\n\tif err != nil {\n\t\t// Connection might be stale, try to reconnect once\n\t\tg.Log().Debug(context.Background(), \"SMTP send failed, attempting reconnection\")\n\n\t\t// Clean up\n\t\te.client.Close()\n\t\te.connected = false\n\t\te.client = nil\n\n\t\t// Try to reconnect\n\t\te.mutex.Unlock()\n\t\tif err := e.Connect(); err != nil {","sourceCodeStart":302,"sourceCodeEnd":338,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/mail_service/sending.go#L302-L338","documentation":"Send() found the sender disconnected (connected==false or client==nil) and attempted an on-demand Connect(); this error wraps that Connect failure. The wrapped error is one of the connect-time errors (TLS dial, SMTP dial, STARTTLS, or auth), so diagnose by unwrapping. The mutex is unlocked/relocked around the blocking Connect call so it is not held during network I/O.","triggerScenarios":"First Send on a never-connected sender, or Send after Disconnect/server idle timeout, where Connect() fails — bad host/port, network down, or rejected credentials.","commonSituations":"Long-lived sender left idle until the server dropped the connection, then reconnect fails because credentials expired; misconfigured host discovered only at first send; transient network outage at send time; container lost DNS/egress.","solutions":["Unwrap the error chain (errors.Unwrap / errors.As) to see whether it was dial, TLS, or auth, and fix that specific root cause.","Call Connect() explicitly at sender creation/startup so failures surface early rather than at first Send.","Add retry with backoff around reconnects for transient network errors before surfacing to the caller.","Verify SMTP host/port/credentials in the environment the sender was constructed from."],"exampleFix":"// before\nif err := e.Connect(); err != nil {\n    e.mutex.Lock()\n    return fmt.Errorf(\"failed to connect: %w\", err)\n}\n// after\nif err := e.Connect(); err != nil {\n    e.mutex.Lock()\n    var netErr net.Error\n    if errors.As(err, &netErr) && netErr.Timeout() {\n        time.Sleep(2 * time.Second)\n        if retryErr := e.Connect(); retryErr == nil {\n            return nil // continue send flow\n        }\n    }\n    return fmt.Errorf(\"failed to connect: %w\", err)\n}","handlingStrategy":"try-catch","validationCode":"if err := sender.Connect(); err != nil {\n    return fmt.Errorf(\"SMTP pre-flight connect failed: %w\", err)\n}\n// sender is now warm; Send() will skip its lazy-connect path","typeGuard":"func isConnectErr(err error) bool {\n    return strings.Contains(err.Error(), \"failed to connect\")\n}","tryCatchPattern":"if err := sender.Send(msg, rcpts); err != nil {\n    if isConnectErr(err) {\n        var dnsErr *net.DNSError\n        switch {\n        case errors.As(err, &dnsErr):\n            return fmt.Errorf(\"bad SMTP host config: %w\", err)\n        case strings.Contains(err.Error(), \"SMTP auth\"):\n            return ErrBadCredentials\n        default:\n            time.Sleep(2 * time.Second) // transient: retry once\n            return sender.Send(msg, rcpts)\n        }\n    }\n    return err\n}","preventionTips":["Connect explicitly at sender creation so config errors surface early.","Unwrap the error chain — the root cause is always a dial/TLS/auth error from Connect.","Retry only transient (timeout/unreachable) errors; auth failures must not be retried."],"tags":["smtp","connection","retry","reconnect"],"backgroundTag":"smtp-connection-failed","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}