XTLS/Xray-core · error

User ${email} already exists.

Error message

User ${email} already exists.

What it means

Validator.Add rejected a trojan user because another user with the same email (compared lowercased) is already registered in this inbound. Emails are the user-facing key for management APIs (stats, del), so uniqueness (case-insensitive) is enforced at add time.

Source

Thrown at proxy/trojan/validator.go:23

	"sync"

	"github.com/xtls/xray-core/common/errors"
	"github.com/xtls/xray-core/common/protocol"
)

// Validator stores valid trojan users.
type Validator struct {
	// Considering email's usage here, map + sync.Mutex/RWMutex may have better performance.
	email sync.Map
	users sync.Map
}

// Add a trojan user, Email must be empty or unique.
func (v *Validator) Add(u *protocol.MemoryUser) error {
	if u.Email != "" {
		_, loaded := v.email.LoadOrStore(strings.ToLower(u.Email), u)
		if loaded {
			return errors.New("User ", u.Email, " already exists.")
		}
	}
	v.users.Store(hexString(u.Account.(*MemoryAccount).Key), u)
	return nil
}

// Del a trojan user with a non-empty Email.
func (v *Validator) Del(e string) error {
	if e == "" {
		return errors.New("Email must not be empty.")
	}
	le := strings.ToLower(e)
	u, _ := v.email.Load(le)
	if u == nil {
		return errors.New("User ", e, " not found.")
	}
	v.email.Delete(le)
	v.users.Delete(hexString(u.(*protocol.MemoryUser).Account.(*MemoryAccount).Key))

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. Remove or rename the duplicate email in the inbound's clients list before reloading
  2. If driving via API, list existing users first and skip/replace instead of re-adding
  3. Remember comparison is case-insensitive: 'A@x' collides with 'a@x'

Example fix

// before
"clients": [
  {"password": "p1", "email": "user@x"},
  {"password": "p2", "email": "user@x"}
]
// after
"clients": [
  {"password": "p1", "email": "user@x"},
  {"password": "p2", "email": "user2@x"}
]
Defensive patterns

Strategy: validation

Validate before calling

// before adding, check for the (case-insensitive) email collision
func canAdd(v *Validator, email string) bool {
    if email == "" { return true }
    _, exists := v.email.Load(strings.ToLower(email))
    return !exists
}

Try / catch

if err := validator.Add(user); err != nil {
    if strings.Contains(err.Error(), "already exists") {
        // idempotent provisioning: treat as success or update-in-place
        return nil
    }
    return err
}

Prevention

When it happens

Trigger: Calling the trojan inbound's Add with an email that exists — typically via the API handler (proxy.AddInboundHandler / handler.service) or config reload that merges duplicate client entries.

Common situations: Copy-pasted 'clients' entries with the same email in config; API-driven user provisioning retrying after a partial success; email differing only by case ('Alice' vs 'alice').

Related errors


AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15). Data as JSON: /api/errors/2fadce0aa748908d. Report an issue: GitHub.