Billionmail/BillionMail · error
new SMTP client: %w
Error message
new SMTP client: %w
What it means
This error is returned when smtp.NewClient(conn, e.Host) fails after the TLS connection was established. NewClient reads the server greeting (220 banner); it errors if the connection was closed, the greeting is malformed, or the peer is not an SMTP server. The code closes conn before returning to avoid leaking the socket.
Source
Thrown at core/internal/service/mail_service/sending.go:220
e.connected = true
return nil
}
// connectWithSSL establishes a secure SMTP connection
func (e *EmailSender) connectWithSSL() error {
conn, err := tls.Dial("tcp", net.JoinHostPort(e.Host, e.Port), &tls.Config{
MinVersion: tls.VersionTLS12,
InsecureSkipVerify: true,
ServerName: e.SNI,
})
if err != nil {
return fmt.Errorf("TLS dial: %w", err)
}
client, err := smtp.NewClient(conn, e.Host)
if err != nil {
conn.Close()
return fmt.Errorf("new SMTP client: %w", err)
}
auth := smtp.PlainAuth("", e.UserName, e.Password, e.Host)
if err = client.Auth(auth); err != nil {
client.Close()
return fmt.Errorf("SMTP auth: %w", err)
}
e.client = client
return nil
}
// connectPlain establishes a plain SMTP connection
func (e *EmailSender) connectPlain() error {
client, err := smtp.Dial(net.JoinHostPort(e.Host, e.Port))
if err != nil {
return fmt.Errorf("SMTP dial: %w", err)View on GitHub (pinned to fc36c76c05)
Solutions
- Confirm the port speaks SMTPS (usually 465); test with openssl s_client -connect host:port and check the banner starts with '220'.
- If the server only supports STARTTLS, switch the sender to the plain path (port 587) instead of implicit SSL.
- Inspect the wrapped error (errors.Unwrap) — 'EOF' usually means the peer closed immediately; a protocol error means it is not SMTP.
- Check TLS-terminating middleboxes/proxies and ensure they pass through to the SMTP backend.
Example fix
// before
client, err := smtp.NewClient(conn, e.Host)
// after
client, err := smtp.NewClient(conn, e.Host)
if err != nil {
conn.Close()
return fmt.Errorf("new SMTP client for %s: %w (is port %s really SMTPS?)", e.Host, err, e.Port)
} Defensive patterns
Strategy: validation
Validate before calling
func verifySMTPSBanner(host, port string) error {
conn, err := tls.DialWithDialer(&net.Dialer{Timeout: 5 * time.Second}, "tcp", net.JoinHostPort(host, port), &tls.Config{ServerName: host})
if err != nil {
return err
}
defer conn.Close()
buf := make([]byte, 256)
conn.SetReadDeadline(time.Now().Add(5 * time.Second))
n, err := conn.Read(buf)
if err != nil || n < 3 || string(buf[:3]) != "220" {
return fmt.Errorf("not an SMTPS endpoint (greeting: %q)", buf[:n])
}
return nil
} Type guard
func isNotSMTPPeer(err error) bool {
var protoErr *textproto.Error
return errors.Is(err, io.EOF) || errors.As(err, &protoErr)
} Try / catch
if err := sender.Send(msg, rcpts); err != nil {
if strings.Contains(err.Error(), "new SMTP client") {
// peer greeted incorrectly: verify port/protocol, then alert ops
log.Printf("endpoint %s:%s is not speaking SMTPS", host, port)
}
} Prevention
- Only use implicit TLS against ports that actually speak SMTPS (465).
- Verify with openssl s_client that the banner begins with 220 before configuring.
- Avoid pointing the sender at TLS-terminating proxies that do not proxy raw SMTP.
When it happens
Trigger: connectWithSSL is called (Connect on a secure sender), tls.Dial succeeds, but the server does not send a valid SMTP 220 greeting — e.g. the port hosts HTTP, a non-SMTP TLS service, or a proxy that closes the connection.
Common situations: Pointing the client at port 443/993/other TLS service that is not SMTP; a TLS-terminating proxy (HAProxy/nginx) accepting the handshake but closing the backend; server greeting delayed past a timeout causing EOF; connecting implicit-TLS to port 587 so the plain banner arrives corrupted by TLS framing.
Related errors
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/5fafb67309e9b5ae.
Report an issue: GitHub.