Skip to content

Commit 99dd49f

Browse files
committed
fix: add concurrency limit to email invites to prevent rate limiting
- Replace sequential processing with batch-based parallel processing - Limit concurrency to 3 concurrent requests per batch - Resolves P2 issue: unbounded parallel requests causing rate-limit failures Fixes review comment from cubic-dev-ai on PR #28733
1 parent 480972b commit 99dd49f

1 file changed

Lines changed: 34 additions & 15 deletions

File tree

apps/web/modules/event-types/components/AddMembersWithSwitch.tsx

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ const CheckedHostField = ({
8585
const { t } = useLocale();
8686
const inviteMutation = trpc.viewer.teams.inviteMember.useMutation();
8787

88-
// Handle inviting new members by email
88+
// Handle inviting new members by email with concurrency limit
8989
const handleEmailInvite = useCallback(
9090
async (emails: string[]): Promise<{ success: string[]; failed: string[] }> => {
9191
if (!teamId || emails.length === 0) {
@@ -95,20 +95,39 @@ const CheckedHostField = ({
9595
const success: string[] = [];
9696
const failed: string[] = [];
9797

98-
// Invite each email
99-
for (const email of emails) {
100-
try {
101-
await inviteMutation.mutateAsync({
102-
teamId,
103-
usernameOrEmail: email,
104-
role: MembershipRole.MEMBER,
105-
language: "en",
106-
creationSource: CreationSource.WEBAPP,
107-
});
108-
success.push(email);
109-
} catch (error) {
110-
failed.push(email);
111-
}
98+
// Process emails with concurrency limit to avoid rate limiting
99+
const CONCURRENCY_LIMIT = 3;
100+
const batches: string[][] = [];
101+
102+
for (let i = 0; i < emails.length; i += CONCURRENCY_LIMIT) {
103+
batches.push(emails.slice(i, i + CONCURRENCY_LIMIT));
104+
}
105+
106+
for (const batch of batches) {
107+
const results = await Promise.all(
108+
batch.map(async (email) => {
109+
try {
110+
await inviteMutation.mutateAsync({
111+
teamId,
112+
usernameOrEmail: email,
113+
role: MembershipRole.MEMBER,
114+
language: "en",
115+
creationSource: CreationSource.WEBAPP,
116+
});
117+
return { email, success: true };
118+
} catch (error) {
119+
return { email, success: false };
120+
}
121+
})
122+
);
123+
124+
results.forEach((result) => {
125+
if (result.success) {
126+
success.push(result.email);
127+
} else {
128+
failed.push(result.email);
129+
}
130+
});
112131
}
113132

114133
return { success, failed };

0 commit comments

Comments
 (0)