{"record":{"id":"53dc67ab4e135b8e","repo":"langgenius/dify","slug":"email-send-ip-limit-53dc67","errorCode":"email_send_ip_limit","errorMessage":"Too many emails have been sent from this IP address recently. Please try again later.","messagePattern":"Too many emails have been sent from this IP address recently\\. Please try again later\\.","errorType":"error_code","errorClass":"EmailSendIpLimitError","httpStatus":429,"severity":"warning","filePath":"api/controllers/console/auth/login.py","lineNumber":255,"sourceCode":"            language=language,\n            is_allow_register=FeatureService.get_system_features().is_allow_register,\n        )\n\n        return SimpleResultDataResponse(result=\"success\", data=token).model_dump(mode=\"json\")\n\n\n@console_ns.route(\"/email-code-login\")\nclass EmailCodeLoginSendEmailApi(Resource):\n    @setup_required\n    @console_ns.expect(console_ns.models[EmailPayload.__name__])\n    @console_ns.response(200, \"Success\", console_ns.models[SimpleResultDataResponse.__name__])\n    @model_validate(EmailPayload)\n    def post(self, req_data: EmailPayload):\n        normalized_email = req_data.email.lower()\n\n        ip_address = extract_remote_ip(request)\n        if AccountService.is_email_send_ip_limit(ip_address):\n            raise EmailSendIpLimitError()\n\n        if req_data.language is not None and req_data.language == \"zh-Hans\":\n            language = \"zh-Hans\"\n        else:\n            language = \"en-US\"\n        try:\n            account = _get_account_with_case_fallback(req_data.email)\n        except AccountRegisterError:\n            raise AccountInFreezeError()\n\n        if account is None:\n            if FeatureService.get_system_features().is_allow_register:\n                token = AccountService.send_email_code_login_email(email=normalized_email, language=language)\n            else:\n                raise AccountNotFound()\n        else:\n            token = AccountService.send_email_code_login_email(account=account, language=language)\n","sourceCodeStart":237,"sourceCodeEnd":273,"githubUrl":"https://github.com/langgenius/dify/blob/ef8544b173fd6cd7a8e71df2cab576e52bebbfbc/api/controllers/console/auth/login.py#L237-L273","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"# before\nip_address = extract_remote_ip(request)\nif AccountService.is_email_send_ip_limit(ip_address):\n    raise EmailSendIpLimitError()\n# after - return retry-after hint based on freeze TTL\nif AccountService.is_email_send_ip_limit(ip_address):\n    ttl = redis_client.ttl(f'email_send_ip_limit_freeze:{ip_address}')\n    raise EmailSendIpLimitError(description=f'IP rate limited. Retry in {ttl}s')","handlingStrategy":"retry","validationCode":"# Client-side: track last request time per IP/UA and throttle locally\nif time.time() - last_send_ts < MIN_INTERVAL:\n    show_message('Please wait before requesting another code.')\n    return\nsubmit_email_code_request(email)","typeGuard":"null","tryCatchPattern":"from controllers.console.error import EmailSendIpLimitError\ntry:\n    request_code(email)\nexcept EmailSendIpLimitError:\n    schedule_retry(after_seconds=3600)  # freeze window is 1 hour\n    suggest_password_login()","preventionTips":["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."],"tags":["auth","email-code-login","rate-limit","ip","redis","abuse-prevention"],"backgroundTag":null,"analyzedSha":"ef8544b173fd6cd7a8e71df2cab576e52bebbfbc","analyzedAt":"2026-08-12T05:15:17.394Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}