langgenius/dify · warning · EmailSendIpLimitError

email_send_ip_limit

email_send_ip_limit

Error message

Too many emails have been sent from this IP address recently. Please try again later.

What it means

Raised by EmailSendIpLimitError in ForgotPasswordSendApi.post when AccountService.is_email_send_ip_limit(ip_address) returns true. The IP-based limiter caps how many password-reset emails can originate from one IP within a window, preventing email-bomb abuse of the reset endpoint.

Source

Thrown at api/controllers/console/auth/forgot_password.py:77

class ForgotPasswordSendEmailApi(Resource):
    @console_ns.doc("send_forgot_password_email")
    @console_ns.doc(description="Send password reset email")
    @console_ns.expect(console_ns.models[ForgotPasswordSendPayload.__name__])
    @console_ns.response(
        200,
        "Email sent successfully",
        console_ns.models[ForgotPasswordEmailResponse.__name__],
    )
    @console_ns.response(400, "Invalid email or rate limit exceeded")
    @setup_required
    @email_password_login_enabled
    @model_validate(ForgotPasswordSendPayload)
    def post(self, req_data: ForgotPasswordSendPayload):
        normalized_email = req_data.email.lower()

        ip_address = extract_remote_ip(request)
        if AccountService.is_email_send_ip_limit(ip_address):
            raise EmailSendIpLimitError()

        if req_data.language is not None and req_data.language == "zh-Hans":
            language = "zh-Hans"
        else:
            language = "en-US"

        account = AccountService.get_account_by_email_with_case_fallback(req_data.email, session=db.session())

        token = AccountService.send_reset_password_email(
            account=account,
            email=normalized_email,
            language=language,
            is_allow_register=FeatureService.get_system_features().is_allow_register,
        )

        return {"result": "success", "data": token}

View on GitHub (pinned to ef8544b173)

Solutions

  1. Wait for the IP rate-limit window to expire before requesting another reset email.
  2. Spread test traffic across multiple egress IPs or mock is_email_send_ip_limit in tests.
  3. If the limit is too aggressive for a shared NAT, have an operator tune the rate-limit config for email-send IP caps.
  4. Have the client debounce the 'send reset email' button and show the cooldown to the user.

Example fix

// before
<button onClick={() => sendReset(email)}>Resend</button>
// after: debounce + cooldown
<button disabled={!canResend} onClick={() => sendReset(email)}>Resend</button>
Defensive patterns

Strategy: retry

Validate before calling

// Debounce resend and respect a local cooldown
if (Date.now() - lastSentAt < 60_000) {
  warnTooSoon();
  return;
}

Try / catch

try {
  await sendReset({ email });
} catch (e) {
  if (e.code === 'email_send_ip_limit') showIpCooldown();
  else throw e;
}

Prevention

When it happens

Trigger: POST /console/api/forgot-password/email-send from an IP that has already triggered too many reset emails in the window. The check runs before the account is even looked up.

Common situations: Shared office/NAT IP with many users requesting resets; automated tests reusing one egress IP; a legitimate user clicking 'resend' rapidly; an attacker abusing the unauthenticated endpoint. VPN users all sharing one exit IP.

Related errors


AI-assisted analysis of langgenius/dify@ef8544b173 (2026-08-12). Data as JSON: /api/errors/96f666c2964812fd. Report an issue: GitHub.