discourse/discourse · warning · Invite::UserExists

invite.existing_user_already_redemeed

Error message

invite.existing_user_already_redemeed

What it means

InviteRedeemer#can_redeem_invite? raises Invite::UserExists with invite.existing_user_already_redemeed when an anonymous redeemer resolves to an existing user who already redeemed this invite (InvitedUser record exists).

Source

Thrown at app/models/invite_redeemer.rb:204

    # prefill the email and do not let the user modify it.
    #
    # Note that an invite link can also have a domain scope which must be checked.
    email_to_check = redeeming_user&.email || email

    if invite.email.present? && !invite.email_matches?(email_to_check)
      raise ActiveRecord::RecordNotSaved.new(I18n.t("invite.not_matching_email"))
    end

    if invite.domain.present? && !invite.domain_matches?(email_to_check)
      raise ActiveRecord::RecordNotSaved.new(I18n.t("invite.domain_not_allowed"))
    end

    # Anon user is trying to redeem an invitation, if an existing user already
    # redeemed it then we cannot redeem now.
    redeeming_user ||= User.where(admin: false, staged: false).find_by_email(email)
    if redeeming_user.present? &&
         InvitedUser.exists?(user_id: redeeming_user.id, invite_id: invite.id)
      raise Invite::UserExists.new(I18n.t("invite.existing_user_already_redemeed"))
    end

    true
  end

  # Note that the invited_user is returned by #redeemed, so other places
  # (e.g. the InvitesController) can perform further actions on it, this
  # is why things like send_welcome_message are set without being saved
  # on the model.
  def invited_user
    return @invited_user if defined?(@invited_user)

    # The redeeming user is an already logged in user or a user who is
    # activating their account who is redeeming the invite,
    # which is valid for existing users to be invited to topics or groups.
    if redeeming_user.present?
      @invited_user = redeeming_user
      return @invited_user

View on GitHub (pinned to 1b2d7253e8)

Solutions

  1. Log in normally - the account already has access from the earlier redemption
  2. In custom flows, rescue Invite::UserExists and treat it as already-redeemed, routing to login

Example fix

# before
InviteRedeemer.redeem(invite, email: email)

# after
begin
  InviteRedeemer.redeem(invite, email: email)
rescue Invite::UserExists
  # already redeemed; route to login
end
Defensive patterns

Strategy: try-catch

Validate before calling

return if InvitedUser.exists?(user_id: redeeming_user.id, invite_id: invite.id)

Try / catch

rescue Invite::UserExists; treat as already-redeemed and route to login

Prevention

When it happens

Trigger: Opening an invite link while logged out with the email of a user who already redeemed it; re-clicking an invite after successful redemption in another browser.

Common situations: Users re-opening old invite links; double-submission of the redemption form.

Related errors


AI-assisted analysis of discourse/discourse@1b2d7253e8 (2026-08-26). Data as JSON: /api/errors/e4a2a5fd5a245535. Report an issue: GitHub.