{"record":{"id":"31f0a6121d51e0a6","repo":"docusealco/docuseal","slug":"too-many-attempts","errorCode":null,"errorMessage":"Too many attempts.","messagePattern":"Too many attempts\\.","errorType":"validation","errorClass":"Submitters::UnableToSendCode","httpStatus":null,"severity":"warning","filePath":"lib/submitters.rb","lineNumber":250,"sourceCode":"\n    filename = filename.gsub('{submission.completed_at}') do\n      completed_at = submitter.submission.completed_at ||\n                     submitter.submission.submitters.select(&:completed_at).max_by(&:completed_at).completed_at\n\n      I18n.l(completed_at.in_time_zone(submitter.account.timezone), format: :short)\n    end\n\n    \"#{filename}.#{blob.filename.extension}\"\n  end\n\n  def send_shared_link_email_verification_code(submitter, request:)\n    RateLimit.call(\"send-otp-code-#{request.remote_ip}\", limit: 2, ttl: 45.seconds, enabled: true)\n\n    TemplateMailer.otp_verification_email(submitter.submission.template, email: submitter.email).deliver_later!\n  rescue RateLimit::LimitApproached\n    Rollbar.warning(\"Limit verification code for template: #{submitter.submission.template.id}\") if defined?(Rollbar)\n\n    raise UnableToSendCode, I18n.t('too_many_attempts')\n  end\n\n  def verify_link_otp!(otp, submitter)\n    return false if otp.blank?\n\n    RateLimit.call(\"verify-2fa-code-#{Digest::MD5.base64digest(submitter.email)}\",\n                   limit: 2, ttl: 45.seconds, enabled: true)\n\n    link_2fa_key = [submitter.email.downcase.squish, submitter.submission.template.slug].join(':')\n\n    raise InvalidOtp, I18n.t(:invalid_code) unless EmailVerificationCodes.verify(otp, link_2fa_key)\n\n    true\n  end\n\n  def build_document_urls(submitter, ttl: FILES_TTL)\n    filename_format = AccountConfig.find_or_initialize_by(account_id: submitter.account_id,\n                                                          key: AccountConfig::DOCUMENT_FILENAME_FORMAT_KEY)&.value","sourceCodeStart":232,"sourceCodeEnd":268,"githubUrl":"https://github.com/docusealco/docuseal/blob/004a22c1c88109c7ba0b567df011a8cb13894001/lib/submitters.rb#L232-L268","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Wait 45 seconds since the last successful send, then request again — the counter key expires with the ttl.","Rescue Submitters::UnableToSendCode at the controller and return 429 with the too_many_attempts message instead of a 500.","Disable the resend button client-side for 45s after each send.","Check the inbox/spam before resending — the first code is still valid."],"exampleFix":"# before (controller)\nSubmitters.send_shared_link_email_verification_code(submitter, request:)\n\n# after\nbegin\n  Submitters.send_shared_link_email_verification_code(submitter, request:)\nrescue Submitters::UnableToSendCode\n  render json: { error: I18n.t('too_many_attempts') }, status: :too_many_requests\nend","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"begin\n  Submitters.send_shared_link_email_verification_code(submitter, request:)\nrescue Submitters::UnableToSendCode\n  render json: { error: I18n.t('too_many_attempts') }, status: :too_many_requests\nend","preventionTips":["Disable the 'send code' button for 45s after each send (matches limit: 2, ttl: 45.seconds).","Check spam before resending — earlier codes remain valid.","Remember the limit is per remote IP, so shared NAT/VPN offices share the budget.","The counter is per-process memory; clustered deployments may allow slightly more attempts."],"tags":["docuseal","rate-limit","otp","email-verification"],"backgroundTag":"rate-limit-exceeded","analyzedSha":"004a22c1c88109c7ba0b567df011a8cb13894001","analyzedAt":"2026-08-21T13:38:23.343Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}