{"record":{"id":"a98a48b558ed31fd","repo":"Billionmail/BillionMail","slug":"smtp-starttls-w","errorCode":null,"errorMessage":"SMTP STARTTLS: %w","messagePattern":"SMTP STARTTLS: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/mail_service/sending.go","lineNumber":249,"sourceCode":"\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}\n\t} else {\n\t\tauth = smtp.PlainAuth(\"\", e.UserName, e.Password, e.Host)\n\t}\n\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\treturn nil","sourceCodeStart":231,"sourceCodeEnd":267,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/mail_service/sending.go#L231-L267","documentation":"This error is returned when client.StartTLS fails after a successful plain dial on port 587. The server either rejected the STARTTLS command (e.g. 454 TLS not available), does not support it, or the subsequent TLS handshake failed. The client is closed before returning so no socket leaks.","triggerScenarios":"connectPlain dials a port-587 server and calls client.StartTLS with MinVersion TLS 1.2 and ServerName e.SNI; the command fails or the handshake fails because STARTTLS is unsupported, e.SNI is empty/mismatched, or the server cannot negotiate TLS 1.2+.","commonSituations":"Configuring port 587 on a relay that only offers plaintext or already-TLS service; e.SNI left empty so the handshake sends wrong/no SNI and strict servers abort; old server limited to TLS 1.0/1.1 while MinVersion is fixed at 1.2; middleboxes stripping STARTTLS capabilities.","solutions":["Check the server advertises STARTTLS in EHLO (swaks or openssl s_client -starttls smtp -connect host:587).","Set e.SNI to the SMTP hostname so the TLS handshake presents the correct ServerName; an empty SNI is a frequent cause.","If STARTTLS is genuinely unavailable on the relay, use port 25 without STARTTLS or port 465 implicit TLS instead.","Check the wrapped alert code: handshake failures with old servers require server upgrades since MinVersion is fixed at TLS 1.2."],"exampleFix":"// before\nif err = client.StartTLS(&tls.Config{\n    MinVersion:         tls.VersionTLS12,\n    InsecureSkipVerify: true,\n    ServerName:         e.SNI,\n}); err != nil {\n    client.Close()\n    return fmt.Errorf(\"SMTP STARTTLS: %w\", err)\n}\n// after\nserverName := e.SNI\nif serverName == \"\" {\n    serverName = e.Host // fall back so SNI is always sent\n}\nif err = client.StartTLS(&tls.Config{\n    MinVersion:         tls.VersionTLS12,\n    InsecureSkipVerify: true,\n    ServerName:         serverName,\n}); err != nil {\n    client.Close()\n    return fmt.Errorf(\"SMTP STARTTLS to %s: %w\", serverName, err)\n}","handlingStrategy":"validation","validationCode":"func supportsSTARTTLS(host, port, serverName string) error {\n    conn, err := smtp.Dial(net.JoinHostPort(host, port))\n    if err != nil {\n        return err\n    }\n    defer conn.Close()\n    if ok, _ := conn.Extension(\"STARTTLS\"); !ok {\n        return fmt.Errorf(\"%s:%s does not advertise STARTTLS\", host, port)\n    }\n    return conn.StartTLS(&tls.Config{MinVersion: tls.VersionTLS12, ServerName: serverName})\n}","typeGuard":"func isStartTLSErr(err error) bool {\n    var protoErr *textproto.Error\n    return errors.As(err, &protoErr) || errors.Is(err, tls.RecordHeaderError{}) || strings.Contains(err.Error(), \"STARTTLS\")\n}","tryCatchPattern":"if err := sender.Send(msg, rcpts); err != nil {\n    if strings.Contains(err.Error(), \"SMTP STARTTLS\") {\n        // fall back to port 465 implicit TLS or plaintext port 25 relay\n        log.Printf(\"STARTTLS failed on %s:%s, switching transport\", host, port)\n    }\n}","preventionTips":["Always set SNI/ServerName for STARTTLS — empty SNI is a common handshake breaker.","Verify the EHLO capabilities include STARTTLS before selecting port 587.","Keep server TLS versions >= 1.2 to match the client's MinVersion."],"tags":["smtp","tls","starttls","handshake"],"backgroundTag":"starttls-failed","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}