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 POST /console/api/email-code-login (HTTP 429, code email_send_ip_limit) when AccountService.is_email_send_ip_limit(ip) returns true. The IP is rate-limited in three tiers: per-minute count (EMAIL_SEND_IP_LIMIT_PER_MINUTE), a 10-minute first-strike window, and a 1-hour freeze key set once the hourly strike is exceeded.
Source
Thrown at api/controllers/console/auth/login.py:255
language=language,
is_allow_register=FeatureService.get_system_features().is_allow_register,
)
return SimpleResultDataResponse(result="success", data=token).model_dump(mode="json")
@console_ns.route("/email-code-login")
class EmailCodeLoginSendEmailApi(Resource):
@setup_required
@console_ns.expect(console_ns.models[EmailPayload.__name__])
@console_ns.response(200, "Success", console_ns.models[SimpleResultDataResponse.__name__])
@model_validate(EmailPayload)
def post(self, req_data: EmailPayload):
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"
try:
account = _get_account_with_case_fallback(req_data.email)
except AccountRegisterError:
raise AccountInFreezeError()
if account is None:
if FeatureService.get_system_features().is_allow_register:
token = AccountService.send_email_code_login_email(email=normalized_email, language=language)
else:
raise AccountNotFound()
else:
token = AccountService.send_email_code_login_email(account=account, language=language)
View on GitHub (pinned to ef8544b173)
Solutions
- Wait up to 1 hour for the email_send_ip_limit_freeze:<ip> Redis key to expire, then retry.
- Switch to a different network/IP or use password login instead of email-code login.
- An operator can raise EMAIL_SEND_IP_LIMIT_PER_MINUTE or clear the freeze key: DEL email_send_ip_limit_freeze:<ip>.
- For legitimate shared-IP deployments, tune the limit in dify_config to match expected concurrent users.
Example fix
# before
ip_address = extract_remote_ip(request)
if AccountService.is_email_send_ip_limit(ip_address):
raise EmailSendIpLimitError()
# after - return retry-after hint based on freeze TTL
if AccountService.is_email_send_ip_limit(ip_address):
ttl = redis_client.ttl(f'email_send_ip_limit_freeze:{ip_address}')
raise EmailSendIpLimitError(description=f'IP rate limited. Retry in {ttl}s') Defensive patterns
Strategy: retry
Validate before calling
# Client-side: track last request time per IP/UA and throttle locally
if time.time() - last_send_ts < MIN_INTERVAL:
show_message('Please wait before requesting another code.')
return
submit_email_code_request(email) Type guard
null
Try / catch
from controllers.console.error import EmailSendIpLimitError
try:
request_code(email)
except EmailSendIpLimitError:
schedule_retry(after_seconds=3600) # freeze window is 1 hour
suggest_password_login() Prevention
- Throttle email-code requests client-side to stay well under the per-minute cap.
- Offer password login as a fallback when the IP is rate-limited.
- For shared-IP deployments, tune EMAIL_SEND_IP_LIMIT_PER_MINUTE to expected load.
When it happens
Trigger: POST /console/api/email-code-login from an IP that exceeded EMAIL_SEND_IP_LIMIT_PER_MINUTE in the current minute and has already triggered the hourly strike counter, or from an IP whose email_send_ip_limit_freeze:<ip> key is still set (1-hour freeze).
Common situations: Shared office NAT IP sending many code-login emails; automated tests hammering the endpoint from one IP; a script abusing the email-code-login send; users behind a corporate proxy pooled on a single egress IP.
Related errors
- email_code_login_limit
- invalid_or_expired_token
- email_send_ip_limit
- account_in_freeze
- email_register_limit
AI-assisted analysis of langgenius/dify@ef8544b173 (2026-08-12).
Data as JSON: /api/errors/53dc67ab4e135b8e.
Report an issue: GitHub.