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
- Wait for the IP rate-limit window to expire before requesting another reset email.
- Spread test traffic across multiple egress IPs or mock is_email_send_ip_limit in tests.
- If the limit is too aggressive for a shared NAT, have an operator tune the rate-limit config for email-send IP caps.
- 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
- Debounce the 'send reset email' button with a visible cooldown.
- For tests, spread traffic across egress IPs or mock is_email_send_ip_limit.
- On shared NAT, tune the server-side IP cap to fit legitimate concurrency.
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
- email_password_reset_limit
- email_send_ip_limit
- account_in_freeze
- email_register_limit
- invalid_or_expired_token
AI-assisted analysis of langgenius/dify@ef8544b173 (2026-08-12).
Data as JSON: /api/errors/96f666c2964812fd.
Report an issue: GitHub.