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_userView on GitHub (pinned to 1b2d7253e8)
Solutions
- Log in normally - the account already has access from the earlier redemption
- 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
- Expire single-use invite links after first redemption
- Make redemption flows idempotent by rescuing UserExists as success
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
- user_exists_error_msg(email)
- login.incorrect_username_email_or_password
- invite.not_matching_email
- invite.domain_not_allowed
- Discourse::InvalidAccess
AI-assisted analysis of discourse/discourse@1b2d7253e8 (2026-08-26).
Data as JSON: /api/errors/e4a2a5fd5a245535.
Report an issue: GitHub.