docusealco/docuseal · warning · Submitters::UnableToSendCode

Too many attempts.

Error message

Too many attempts.

What it means

Submitters.send_shared_link_email_verification_code (lib/submitters.rb:250) re-raises RateLimit::LimitApproached as UnableToSendCode with the 'Too many attempts.' message. The throttle is RateLimit.call("send-otp-code-#{remote_ip}", limit: 2, ttl: 45.seconds, enabled: true) — at most 2 verification-code emails per source IP per 45 seconds, counted in a process-local memory store. It also logs a Rollbar warning when defined.

Source

Thrown at lib/submitters.rb:250

    filename = filename.gsub('{submission.completed_at}') do
      completed_at = submitter.submission.completed_at ||
                     submitter.submission.submitters.select(&:completed_at).max_by(&:completed_at).completed_at

      I18n.l(completed_at.in_time_zone(submitter.account.timezone), format: :short)
    end

    "#{filename}.#{blob.filename.extension}"
  end

  def send_shared_link_email_verification_code(submitter, request:)
    RateLimit.call("send-otp-code-#{request.remote_ip}", limit: 2, ttl: 45.seconds, enabled: true)

    TemplateMailer.otp_verification_email(submitter.submission.template, email: submitter.email).deliver_later!
  rescue RateLimit::LimitApproached
    Rollbar.warning("Limit verification code for template: #{submitter.submission.template.id}") if defined?(Rollbar)

    raise UnableToSendCode, I18n.t('too_many_attempts')
  end

  def verify_link_otp!(otp, submitter)
    return false if otp.blank?

    RateLimit.call("verify-2fa-code-#{Digest::MD5.base64digest(submitter.email)}",
                   limit: 2, ttl: 45.seconds, enabled: true)

    link_2fa_key = [submitter.email.downcase.squish, submitter.submission.template.slug].join(':')

    raise InvalidOtp, I18n.t(:invalid_code) unless EmailVerificationCodes.verify(otp, link_2fa_key)

    true
  end

  def build_document_urls(submitter, ttl: FILES_TTL)
    filename_format = AccountConfig.find_or_initialize_by(account_id: submitter.account_id,
                                                          key: AccountConfig::DOCUMENT_FILENAME_FORMAT_KEY)&.value

View on GitHub (pinned to 004a22c1c8)

Solutions

  1. Wait 45 seconds since the last successful send, then request again — the counter key expires with the ttl.
  2. Rescue Submitters::UnableToSendCode at the controller and return 429 with the too_many_attempts message instead of a 500.
  3. Disable the resend button client-side for 45s after each send.
  4. Check the inbox/spam before resending — the first code is still valid.

Example fix

# before (controller)
Submitters.send_shared_link_email_verification_code(submitter, request:)

# after
begin
  Submitters.send_shared_link_email_verification_code(submitter, request:)
rescue Submitters::UnableToSendCode
  render json: { error: I18n.t('too_many_attempts') }, status: :too_many_requests
end
Defensive patterns

Strategy: retry

Try / catch

begin
  Submitters.send_shared_link_email_verification_code(submitter, request:)
rescue Submitters::UnableToSendCode
  render json: { error: I18n.t('too_many_attempts') }, status: :too_many_requests
end

Prevention

When it happens

Trigger: A third 'send code' request within 45 seconds from the same IP on a template shared link; retries after a slow mail send; automated tests or monitoring hammering the endpoint; multiple colleagues behind one NAT IP requesting codes in the same window.

Common situations: Shared office/VPN egress IPs; users clicking 'resend code' repeatedly; frontend auto-retry on timeout; per-process MemoryStore meaning clustered deployments sometimes allow more attempts than configured.

Related errors


AI-assisted analysis of docusealco/docuseal@004a22c1c8 (2026-08-21). Data as JSON: /api/errors/31f0a6121d51e0a6. Report an issue: GitHub.