Billionmail/BillionMail · error

hex decode failed: %w

Error message

hex decode failed: %w

What it means

The password_encode column is expected to hold a hex-encoded string. hex.DecodeString fails if the stored value contains non-hex characters or has odd length, producing 'hex decode failed' with the cause wrapped via %w.

Source

Thrown at core/internal/service/mail_service/sending.go:164

		err = errors.New("Email Sender not configured")
		return
	}

	return
}

// PasswordPlainByEmail
func PasswordPlainByEmail(ctx context.Context, email string) (string, error) {
	val, err := g.DB().Model("mailbox").Where("username", email).Value("password_encode")
	if err != nil {
		return "", fmt.Errorf("query password failed: %w", err)
	}
	if val.IsEmpty() {
		return "", fmt.Errorf("password not found for %s", email)
	}
	rawHex, err := hex.DecodeString(val.String())
	if err != nil {
		return "", fmt.Errorf("hex decode failed: %w", err)
	}
	plainBytes, err := base64.StdEncoding.DecodeString(string(rawHex))
	if err != nil {
		return "", fmt.Errorf("base64 decode failed: %w", err)
	}
	return string(plainBytes), nil
}

// Close closes the SMTP connection
func (e *EmailSender) Close() {
	_ = e.Disconnect()
}

// Connect establishes an SMTP connection
func (e *EmailSender) Connect() error {
	e.mutex.Lock()
	defer e.mutex.Unlock()

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Inspect the stored value: it must be valid hex (even length, [0-9a-f] only).
  2. Re-encode the password as base64 then hex before storing, or use the app's own password-set flow.
  3. Reset the mailbox password via the admin UI so it is stored in the expected format.
  4. Check for a schema/version mismatch — older versions may have stored passwords differently.

Example fix

// before
UPDATE mailbox SET password_encode='mypassword' WHERE username='x';
// after
-- store: base64(password) encoded as hex
b64 := base64.StdEncoding.EncodeToString([]byte("mypassword"))
enc := hex.EncodeToString([]byte(b64))
-- UPDATE mailbox SET password_encode='<enc>' WHERE username='x';
Defensive patterns

Strategy: validation

Validate before calling

v, err := g.DB().Model("mailbox").Where("username", email).Value("password_encode")
if err == nil && !v.IsEmpty() {
    if _, derr := hex.DecodeString(v.String()); derr != nil {
        return fmt.Errorf("password_encode for %s is not valid hex; reset the password", email)
    }
}

Type guard

func isHex(s string) bool {
    _, err := hex.DecodeString(s)
    return len(s)%2 == 0 && err == nil
}

Try / catch

pass, err := PasswordPlainByEmail(ctx, email)
if err != nil {
    var hexErr *hex.InvalidByteError
    if errors.As(err, &hexErr) {
        return fmt.Errorf("mailbox %s password stored in wrong format; reset via admin UI", email)
    }
    return err
}

Prevention

When it happens

Trigger: PasswordPlainByEmail reads a password_encode value that was stored in a different format (plain text, raw base64, bcrypt hash) or was corrupted/truncated, so it is not valid hex.

Common situations: Admin pasted a plain password or base64 string directly into the column; another tool/binary wrote the column in its own format; partial row update truncated the value to odd length.

Related errors


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