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

  1. Verify the recipient address(es) are valid and exist; drop bounced/unknown addresses from the list
  2. Ensure the sending IP is allowed to relay: add it to Postfix mynetworks or configure SMTP AUTH for the client
  3. Check /var/log/mail.log for the exact SMTP reply code to distinguish unknown-user (550) vs relay-denied (554) vs temporary (4xx)
  4. 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

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


AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05). Data as JSON: /api/errors/ff3d46773ca3efe9. Report an issue: GitHub.