Billionmail/BillionMail · error
SMTP rcpt: %w
Error message
SMTP rcpt: %w
What it means
This error wraps the failure of the net/smtp Client.Rcpt() call during the RCPT TO phase of an SMTP transaction in doSend. The server rejected one of the recipient addresses, meaning the mail cannot be delivered to that recipient for this transaction. The underlying SMTP server reply is preserved via %w.
Source
Thrown at core/internal/service/mail_service/sending.go:421
// Reset the connection state if sending fails
if err != nil {
if err = e.client.Reset(); err != nil {
g.Log().Warning(context.Background(), "SMTP reset: %w", err)
}
}
}()
// Set the sender
// MAIL FROM
if err = e.client.Mail(e.Email); err != nil {
return fmt.Errorf("SMTP mail: %w", err)
}
// Set the recipients
// RCPT TO
for _, to := range recipients {
if err = e.client.Rcpt(to); err != nil {
return fmt.Errorf("SMTP rcpt: %w", err)
}
}
// Get a writer for the message body
var w io.WriteCloser
w, err = e.client.Data()
if err != nil {
return fmt.Errorf("SMTP data: %w", err)
}
// Write the message
if _, err = w.Write(msg); err != nil {
return fmt.Errorf("SMTP write: %w", err)
}
// Close the writer
if err = w.Close(); err != nil {
return fmt.Errorf("SMTP close writer: %w", err)View on GitHub (pinned to fc36c76c05)
Solutions
- Verify the recipient address(es) are valid and exist; drop bounced/unknown addresses from the list
- Ensure the sending IP is allowed to relay: add it to Postfix mynetworks or configure SMTP AUTH for the client
- Check /var/log/mail.log for the exact SMTP reply code to distinguish unknown-user (550) vs relay-denied (554) vs temporary (4xx)
- If bulk sending, split batches to stay under smtpd_recipient_limit and retry temporary 4xx failures
Example fix
// before
if err = e.client.Rcpt(to); err != nil {
return fmt.Errorf("SMTP rcpt: %w", err)
}
// after
if err = e.client.Rcpt(strings.TrimSpace(to)); err != nil {
return fmt.Errorf("SMTP rcpt %s: %w", to, err) // include address for diagnosis
} Defensive patterns
Strategy: validation
Validate before calling
addrs := strings.Split(strings.TrimSpace(recipientList), ",")
for _, a := range addrs {
if a == "" || !strings.Contains(a, "@") {
return fmt.Errorf("invalid recipient: %q", a)
}
} Try / catch
err := sender.Send(msg, recipients)
var smtpErr *textproto.Error
if errors.As(err, &smtpErr) {
// inspect smtpErr.Code: 4xx = retry later, 5xx = drop recipient
if smtpErr.Code >= 500 { markRecipientBounced(recipient) } else { requeue(recipient) }
} Prevention
- Validate/suppress invalid addresses before sending; keep a suppression list of hard bounces
- Ensure proper relay authorization (mynetworks or SMTP AUTH) on the mail server
- Batch recipients under smtpd_recipient_limit
- Parse the wrapped SMTP reply code to distinguish permanent vs temporary failures
When it happens
Trigger: e.client.Rcpt(to) returned a 4xx/5xx reply for one of the recipient addresses after MAIL FROM was accepted — e.g. '550 relay access denied', '550 5.1.1 user unknown', or '452 too many recipients' while iterating the recipients loop.
Common situations: Sending to a non-existent mailbox on the local domain; trying to relay through a server that doesn't trust the sender IP (mynetworks/sasl not configured); recipient address malformed or empty in the campaign list; RCPT rate/recipient-count limits on Postfix exceeded.
Related errors
- failed to send test email to %d recipient(s): %v
- SMTP data: %w
- SMTP close writer: %w
- SMTP server is required
- SMTP port must be between 1 and 65535
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/ff3d46773ca3efe9.
Report an issue: GitHub.