{"record":{"id":"fbaff62abfe3be39","repo":"toeverything/AFFiNE","slug":"too-many-request-fbaff6","errorCode":"too_many_request","errorMessage":"Too many requests.","messagePattern":"Too many requests\\.","errorType":"http","errorClass":"TooManyRequest","httpStatus":429,"severity":"error","filePath":"packages/backend/server/src/core/workspaces/resolvers/member.ts","lineNumber":245,"sourceCode":"      }));\n    }\n  }\n\n  @Mutation(() => [InviteResult])\n  async inviteMembers(\n    @CurrentUser() me: CurrentUser,\n    @Context() context: GraphqlContext,\n    @Args('workspaceId') workspaceId: string,\n    @Args({ name: 'emails', type: () => [String] }) emails: string[]\n  ): Promise<InviteResult[]> {\n    await this.ac\n      .user(me.id)\n      .workspace(workspaceId)\n      .assert('Workspace.Users.Manage');\n    await this.assertWorkspaceNameCanInvite(workspaceId);\n\n    if (emails.length > 512) {\n      throw new TooManyRequest();\n    }\n\n    const results: InviteResult[] = emails.map(email => ({ email }));\n    const candidates: InviteCandidate[] = [];\n    const seen = new Set<string>();\n    for (const [index, email] of emails.entries()) {\n      try {\n        const normalizedEmail = email.trim().toLowerCase();\n        validators.assertValidEmail(normalizedEmail);\n        if (seen.has(normalizedEmail)) {\n          throw new ActionForbidden('Duplicate invite email.');\n        }\n        seen.add(normalizedEmail);\n\n        const target = await this.models.user.getUserByEmail(normalizedEmail);\n        if (target) {\n          const originRecord = await this.models.workspaceUser.get(\n            workspaceId,","sourceCodeStart":227,"sourceCodeEnd":263,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/workspaces/resolvers/member.ts#L227-L263","documentation":"Thrown by inviteMembers when the emails array has more than 512 entries. The mutation caps a single batch at 512 to bound work and abuse exposure; larger batches are rejected up front as too_many_requests (HTTP 429) before any email is validated.","triggerScenarios":"Calling mutation inviteMembers with an emails array of length > 512.","commonSituations":"Bulk-import integrations that push a full contact list in one call; CSV/paste of a large distribution list; concatenating multiple selections without paging.","solutions":["Chunk emails into batches of <= 512 and call inviteMembers per chunk.","Respect the 512 hard cap in the UI (disable the submit button above it).","For very large imports, prefer a server-side/bulk invite job over interactive calls."],"exampleFix":"// before\ninviteMembers(workspaceId, allEmails); // allEmails.length = 2000\n// after\nfor (const chunk of chunkArray(allEmails, 512)) {\n  await inviteMembers(workspaceId, chunk);\n}","handlingStrategy":"validation","validationCode":"const MAX_INVITES = 512;\nfunction chunkEmails(emails: string[], size = MAX_INVITES): string[][] {\n  const out: string[][] = [];\n  for (let i = 0; i < emails.length; i += size) out.push(emails.slice(i, i + size));\n  return out;\n}\nfor (const c of chunkEmails(emails)) await inviteMembers(workspaceId, c);","typeGuard":"function isWithinInviteLimit(emails: string[], max = 512): boolean {\n  return Array.isArray(emails) && emails.length <= max;\n}","tryCatchPattern":null,"preventionTips":["Disable the invite submit button when the selected count exceeds 512.","Chunk large lists into <= 512 batches.","Use a server-side bulk invite job for very large imports."],"tags":["graphql","rate-limit","validation","workspace","invite"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}