From bdd7f406882a3e662594698a17b71376f7a32934 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Tue, 18 Aug 2026 15:59:51 -0400 Subject: [PATCH] batch identity key emails --- server/lib/sendVirtualApiKeyEmail.ts | 16 ++++++++++++-- .../virtualApiKey/emailIdentityKeys.ts | 21 +++++++++---------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/server/lib/sendVirtualApiKeyEmail.ts b/server/lib/sendVirtualApiKeyEmail.ts index 4eecd180f..c4a6c57d3 100644 --- a/server/lib/sendVirtualApiKeyEmail.ts +++ b/server/lib/sendVirtualApiKeyEmail.ts @@ -7,6 +7,18 @@ import VirtualApiKeyGenerated from "@server/emails/templates/VirtualApiKeyGenera import { formatVirtualApiKeyCredential } from "@server/lib/virtualApiKey"; const EMAIL_GATEWAY_URL_LIMIT = 5; +const VIRTUAL_API_KEY_EMAIL_BATCH_SIZE = 50; + +export async function mapInBatches( + items: T[], + fn: (item: T) => Promise, + batchSize = VIRTUAL_API_KEY_EMAIL_BATCH_SIZE +): Promise { + for (let i = 0; i < items.length; i += batchSize) { + const batch = items.slice(i, i + batchSize); + await Promise.all(batch.map(fn)); + } +} async function listVirtualApiKeyGatewayUrls(params: { orgId: string; @@ -161,7 +173,7 @@ export async function sendVirtualApiKeyEmails(params: { ? `Your identity key for ${params.orgName}` : `Virtual API key for ${params.orgName}`; - for (const to of params.recipients) { + await mapInBatches(params.recipients, async (to) => { await sendEmail( params.isIdentityKey ? IdentityApiKeyGenerated({ @@ -184,5 +196,5 @@ export async function sendVirtualApiKeyEmails(params: { subject } ); - } + }); } diff --git a/server/routers/virtualApiKey/emailIdentityKeys.ts b/server/routers/virtualApiKey/emailIdentityKeys.ts index 4c4963751..c254d462d 100644 --- a/server/routers/virtualApiKey/emailIdentityKeys.ts +++ b/server/routers/virtualApiKey/emailIdentityKeys.ts @@ -12,7 +12,8 @@ import config from "@server/lib/config"; import { getOrCreateUserVirtualApiKey } from "@server/lib/virtualApiKey"; import { sendVirtualApiKeyEmails, - listOrgInferenceGatewayUrls + listOrgInferenceGatewayUrls, + mapInBatches } from "@server/lib/sendVirtualApiKeyEmail"; import type { EmailIdentityKeysResponse } from "@server/routers/virtualApiKey/types"; import rateLimit, { ipKeyGenerator } from "express-rate-limit"; @@ -218,15 +219,13 @@ export async function emailIdentityKeys( const orgName = org?.name || orgId; const gatewayUrls = await listOrgInferenceGatewayUrls(orgId); + const recipients = members.flatMap(({ user }) => + user.email ? [{ user, email: user.email }] : [] + ); let sent = 0; - let skipped = 0; - - for (const { user } of members) { - if (!user.email) { - skipped += 1; - continue; - } + const skipped = members.length - recipients.length; + await mapInBatches(recipients, async ({ user, email }) => { const { key, secret } = await getOrCreateUserVirtualApiKey({ orgId, user, @@ -234,7 +233,7 @@ export async function emailIdentityKeys( }); await sendVirtualApiKeyEmails({ - recipients: [user.email], + recipients: [email], orgName, orgId, keyName: key.name, @@ -242,11 +241,11 @@ export async function emailIdentityKeys( secret, allResources: true, isIdentityKey: true, - accountLabel: user.email || user.name || user.username, + accountLabel: email, gatewayUrls }); sent += 1; - } + }); return response(res, { data: { sent, skipped },