toeverything/AFFiNE · error · TooManyRequest

too_many_request

too_many_request

Error message

Too many requests.

What it means

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.

Source

Thrown at packages/backend/server/src/core/workspaces/resolvers/member.ts:245

      }));
    }
  }

  @Mutation(() => [InviteResult])
  async inviteMembers(
    @CurrentUser() me: CurrentUser,
    @Context() context: GraphqlContext,
    @Args('workspaceId') workspaceId: string,
    @Args({ name: 'emails', type: () => [String] }) emails: string[]
  ): Promise<InviteResult[]> {
    await this.ac
      .user(me.id)
      .workspace(workspaceId)
      .assert('Workspace.Users.Manage');
    await this.assertWorkspaceNameCanInvite(workspaceId);

    if (emails.length > 512) {
      throw new TooManyRequest();
    }

    const results: InviteResult[] = emails.map(email => ({ email }));
    const candidates: InviteCandidate[] = [];
    const seen = new Set<string>();
    for (const [index, email] of emails.entries()) {
      try {
        const normalizedEmail = email.trim().toLowerCase();
        validators.assertValidEmail(normalizedEmail);
        if (seen.has(normalizedEmail)) {
          throw new ActionForbidden('Duplicate invite email.');
        }
        seen.add(normalizedEmail);

        const target = await this.models.user.getUserByEmail(normalizedEmail);
        if (target) {
          const originRecord = await this.models.workspaceUser.get(
            workspaceId,

View on GitHub (pinned to 26c515e050)

Solutions

  1. Chunk emails into batches of <= 512 and call inviteMembers per chunk.
  2. Respect the 512 hard cap in the UI (disable the submit button above it).
  3. For very large imports, prefer a server-side/bulk invite job over interactive calls.

Example fix

// before
inviteMembers(workspaceId, allEmails); // allEmails.length = 2000
// after
for (const chunk of chunkArray(allEmails, 512)) {
  await inviteMembers(workspaceId, chunk);
}
Defensive patterns

Strategy: validation

Validate before calling

const MAX_INVITES = 512;
function chunkEmails(emails: string[], size = MAX_INVITES): string[][] {
  const out: string[][] = [];
  for (let i = 0; i < emails.length; i += size) out.push(emails.slice(i, i + size));
  return out;
}
for (const c of chunkEmails(emails)) await inviteMembers(workspaceId, c);

Type guard

function isWithinInviteLimit(emails: string[], max = 512): boolean {
  return Array.isArray(emails) && emails.length <= max;
}

Prevention

When it happens

Trigger: Calling mutation inviteMembers with an emails array of length > 512.

Common situations: Bulk-import integrations that push a full contact list in one call; CSV/paste of a large distribution list; concatenating multiple selections without paging.

Understand the failure class

Related errors


AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12). Data as JSON: /api/errors/fbaff62abfe3be39. Report an issue: GitHub.