{"record":{"id":"254751a27ec526c7","repo":"overleaf/overleaf","slug":"invalidinstitutionalemailerror","errorCode":null,"errorMessage":"InvalidInstitutionalEmailError","messagePattern":"InvalidInstitutionalEmailError","errorType":"exception","errorClass":"InvalidInstitutionalEmailError","httpStatus":422,"severity":"error","filePath":"services/web/app/src/Features/Institutions/InstitutionsAPI.mjs","lineNumber":229,"sourceCode":"\n  try {\n    await _affiliationRequestFetchNothing({\n      method: 'POST',\n      path: `/api/v2/users/${userId.toString()}/affiliations`,\n      body: {\n        email,\n        university,\n        department,\n        role,\n        confirmedAt,\n        entitlement,\n        rejectIfBlocklisted,\n      },\n      defaultErrorMessage: \"Couldn't create affiliation\",\n    })\n  } catch (error) {\n    if (error.info?.status === 422) {\n      throw new InvalidInstitutionalEmailError(error.message).withCause(error)\n    }\n    throw error\n  }\n\n  if (!university) {\n    return\n  }\n\n  // have notifications delete any ip matcher notifications for this university\n  try {\n    await NotificationsBuilder.promises\n      .ipMatcherAffiliation(userId.toString())\n      .read(university.id)\n  } catch (err) {\n    // log and ignore error\n    logger.err({ err }, 'Something went wrong marking ip notifications read')\n  }\n}","sourceCodeStart":211,"sourceCodeEnd":247,"githubUrl":"https://github.com/overleaf/overleaf/blob/28ad3b03b71cb4311decdcb55c36b33ec10d72db/services/web/app/src/Features/Institutions/InstitutionsAPI.mjs#L211-L247","documentation":"InvalidInstitutionalEmailError is thrown when the affiliations API rejects a new institutional affiliation with HTTP 422, meaning the supplied email address is not a valid institutional (university-domain) email. It wraps the upstream error via withCause so the original API message is preserved.","triggerScenarios":"Calling addAffiliation with an email that the affiliations backend refuses with status 422 (e.g. free-mail domains like gmail.com, malformed address, or a domain not recognized as the university's).","commonSituations":"User typed a personal email instead of their university address; institution's claimed email domains are misconfigured in the institutions service; SSO/metadata changes invalidated previously accepted domains.","solutions":["Have the user enter an email on a domain owned by the institution","Check the institution's confirmed email-domain settings in the institutions admin data","Inspect error.cause / error.message for the specific 422 reason from the affiliations API","If the domain is legitimately institutional, update the institution's email-domain allowlist"],"exampleFix":"// before\nawait addAffiliation(institutionId, { email: user.email })\n// after\nif (!user.email.endsWith('@university.edu')) {\n  throw new InvalidInstitutionalEmailError(`${user.email} is not institutional`)\n}\nawait addAffiliation(institutionId, { email: user.email })","handlingStrategy":"validation","validationCode":"function isLikelyInstitutionalEmail(email, allowedDomains) {\n  const domain = email.split('@')[1]?.toLowerCase()\n  return !!domain && allowedDomains.includes(domain)\n}\nif (!isLikelyInstitutionalEmail(email, institutionDomains)) throw new Error('not institutional')","typeGuard":"function has422Info(e) {\n  return typeof e === 'object' && e !== null && e.info?.status === 422\n}","tryCatchPattern":"try {\n  await addAffiliation(institutionId, { email, role, department })\n} catch (error) {\n  if (error instanceof InvalidInstitutionalEmailError) {\n    notifyUser('Please use your university email address')\n  } else throw error\n}","preventionTips":["Show only institution-owned domains in the email input UI hint","Validate domain against the institution's confirmed domains client-side","Surface the upstream 422 message to the user, not a generic failure"],"tags":["http-422","email-validation","institutions"],"backgroundTag":"invalid-institutional-email","analyzedSha":"28ad3b03b71cb4311decdcb55c36b33ec10d72db","analyzedAt":"2026-09-03T02:10:22.807Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T07:17:11.731Z"}