{"record":{"id":"14f2a6f4dfde50dc","repo":"getredash/redash","slug":"bad-email-address","errorCode":null,"errorMessage":"Bad email address.","messagePattern":"Bad email address\\.","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"redash/handlers/users.py","lineNumber":64,"sourceCode":"\ndef invite_user(org, inviter, user, send_email=True):\n    d = user.to_dict()\n\n    invite_url = invite_link_for_user(user)\n    if settings.email_server_is_configured() and send_email:\n        send_invite_email(inviter, user, invite_url, org)\n    else:\n        d[\"invite_link\"] = invite_url\n\n    return d\n\n\ndef require_allowed_email(email):\n    # `example.com` and `example.com.` are equal - last dot stands for DNS root but usually is omitted\n    _, domain = email.lower().rstrip(\".\").split(\"@\", 1)\n\n    if domain in blacklist or domain in settings.BLOCKED_DOMAINS:\n        abort(400, message=\"Bad email address.\")\n\n\nclass UserListResource(BaseResource):\n    decorators = BaseResource.decorators + [limiter.limit(\"200/day;50/hour\", methods=[\"POST\"])]\n\n    def get_users(self, disabled, pending, search_term):\n        if disabled:\n            users = models.User.all_disabled(self.current_org)\n        else:\n            users = models.User.all(self.current_org)\n\n        if pending is not None:\n            users = models.User.pending(users, pending)\n\n        if search_term:\n            users = models.User.search(users, search_term)\n            self.record_event(\n                {","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/getredash/redash/blob/ca79fe988d81cdac9675b412f3dfcab107bc1fbc/redash/handlers/users.py#L46-L82","documentation":"Raised by require_allowed_email in redash/handlers/users.py when the domain part of the email (normalized: lowercased, trailing dot stripped) is in the internal blacklist or in the settings.BLOCKED_DOMAINS configuration. This is a signup/invite spam guard.","triggerScenarios":"Inviting a user (POST /api/users, or the invite flow) with an email at a blocked domain, e.g. mailinator.com or any domain listed in REDASH_BLOCKED_DOMAINS.","commonSituations":"Disposable-email blocking configured via env var; corporate installs blocking personal-mail domains; testers using throwaway addresses that land on the default blacklist.","solutions":["Use an email at an allowed (non-blocked) domain.","If you administer the instance, review/remove the domain from settings.BLOCKED_DOMAINS.","Normalize the address (lowercase, no trailing dot) before checking against your own list."],"exampleFix":"# before\nclient.post('/api/users', json={'name': 'X', 'email': 'user@mailinator.com'})\n\n# after\nclient.post('/api/users', json={'name': 'X', 'email': 'user@corp.com'})","handlingStrategy":"validation","validationCode":"domain = email.lower().rstrip('.').split('@', 1)[1]\nif domain in blocked_domains:\n    raise ValueError('email domain blocked')","typeGuard":"def is_allowed_email(email: str, blocked: set) -> bool:\n    try:\n        _, domain = email.lower().rstrip('.').split('@', 1)\n    except ValueError:\n        return False\n    return domain not in blocked","tryCatchPattern":null,"preventionTips":["Test invites with real corporate addresses, not disposable mail.","Keep a copy of REDASH_BLOCKED_DOMAINS visible to whoever writes invite scripts."],"tags":["redash","users","email","validation","bad-request"],"backgroundTag":"email-domain-blocked","analyzedSha":"ca79fe988d81cdac9675b412f3dfcab107bc1fbc","analyzedAt":"2026-08-28T18:32:34.637Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}