{"record":{"id":"0b4b8598b2bafb64","repo":"Billionmail/BillionMail","slug":"smtp-dial-w","errorCode":null,"errorMessage":"SMTP dial: %w","messagePattern":"SMTP dial: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/mail_service/sending.go","lineNumber":238,"sourceCode":"\t\treturn fmt.Errorf(\"new SMTP client: %w\", err)\n\t}\n\n\tauth := smtp.PlainAuth(\"\", e.UserName, e.Password, e.Host)\n\tif err = client.Auth(auth); err != nil {\n\t\tclient.Close()\n\t\treturn fmt.Errorf(\"SMTP auth: %w\", err)\n\t}\n\n\te.client = client\n\n\treturn nil\n}\n\n// connectPlain establishes a plain SMTP connection\nfunc (e *EmailSender) connectPlain() error {\n\tclient, err := smtp.Dial(net.JoinHostPort(e.Host, e.Port))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"SMTP dial: %w\", err)\n\t}\n\n\t// Check if STARTTLS is needed\n\tif e.Port == \"587\" {\n\t\tif err = client.StartTLS(&tls.Config{\n\t\t\tMinVersion:         tls.VersionTLS12,\n\t\t\tInsecureSkipVerify: true,\n\t\t\tServerName:         e.SNI,\n\t\t}); err != nil {\n\t\t\tclient.Close()\n\t\t\treturn fmt.Errorf(\"SMTP STARTTLS: %w\", err)\n\t\t}\n\t}\n\n\tvar auth smtp.Auth\n\n\tif e.Port == \"25\" {\n\t\tauth = &customAuth{e.UserName, e.Password}","sourceCodeStart":220,"sourceCodeEnd":256,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/mail_service/sending.go#L220-L256","documentation":"This error wraps smtp.Dial failing in connectPlain — a plain TCP connection to host:port could not be established. It happens at the TCP layer before any SMTP exchange, so causes are DNS failure, connection refused, timeouts, or network unreachability. It is returned for any non-secure sender (ports 25/587).","triggerScenarios":"EmailSender.Connect() with isSecure()==false and smtp.Dial(net.JoinHostPort(e.Host, e.Port)) fails: host not resolvable, port closed/refused, or connection timed out.","commonSituations":"Mail relay hostname typo or stale DNS; local Postfix relay not running (connection refused on 127.0.0.1:25); cloud providers blocking outbound port 25 (AWS/GCP/Azure default); Docker container lacking egress; firewalls dropping 587.","solutions":["Test connectivity: nc -zv <host> <port> from the same machine/container the app runs on.","Check DNS resolution of e.Host (dig/nslookup) and fix the configured hostname if stale.","If relaying via localhost, verify the local MTA is listening (ss -ltnp | grep :25) and running.","If hosted on a cloud provider, confirm outbound port 25 is not blocked; use the provider's relay or port 587/465 instead."],"exampleFix":"// before\nclient, err := smtp.Dial(net.JoinHostPort(e.Host, e.Port))\n// after\nd := net.Dialer{Timeout: 10 * time.Second}\nconn, err := d.Dial(\"tcp\", net.JoinHostPort(e.Host, e.Port))\nif err != nil {\n    return fmt.Errorf(\"SMTP dial: %w (check host %q reachability and firewall rules)\", err, e.Host)\n}\nclient, err := smtp.NewClient(conn, e.Host)","handlingStrategy":"validation","validationCode":"func plainSMTPReachable(host, port string) error {\n    conn, err := net.DialTimeout(\"tcp\", net.JoinHostPort(host, port), 10*time.Second)\n    if err != nil {\n        return fmt.Errorf(\"cannot reach %s:%s: %w\", host, port, err)\n    }\n    conn.Close()\n    return nil\n}\n// e.g. plainSMTPReachable(\"relay.example.com\", \"587\")","typeGuard":"func isDialFailure(err error) bool {\n    var opErr *net.OpError\n    var dnsErr *net.DNSError\n    return errors.As(err, &opErr) || errors.As(err, &dnsErr)\n}","tryCatchPattern":"if err := sender.Send(msg, rcpts); err != nil {\n    var dnsErr *net.DNSError\n    if errors.As(err, &dnsErr) && dnsErr.IsNotFound {\n        return fmt.Errorf(\"SMTP host %q does not resolve — check config\", host)\n    }\n    if errors.Is(err, syscall.ECONNREFUSED) {\n        // retry later or fall back to an alternate relay\n    }\n}","preventionTips":["Pre-check host:port reachability at startup and before campaigns.","Verify DNS and firewall/egress rules, especially cloud provider port-25 blocks.","Ensure the local MTA is running when relaying via localhost."],"tags":["network","smtp","dial","tcp"],"backgroundTag":"connection-refused","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"}