hcengineering/platform · warning · PlatformError
platform.status.WorkspaceRateLimit
platform.status.WorkspaceRateLimit
Error message
WorkspaceRateLimit
What it means
checkRateLimit enforces a per-email invite throttle: if more than 5 invites have been sent to the same email and less than 60 seconds have elapsed since the tracked last send, it throws platform.status.WorkspaceRateLimit. It protects the mail pipeline from flooding and is invoked by createWorkspace and resendInvite.
Source
Thrown at server/account/src/operations.ts:926
path += `&navigateUrl=${encodeURIComponent(navigateUrl.trim())}`
}
const front = getFrontUrl(branding)
const link = concatLink(front, path)
ctx.info(`Created invite link: ${link}`)
return link
}
function checkRateLimit (email: string, workspaceName: string): void {
const now = Date.now()
const lastInvites = invitesSend.get(email)
if (lastInvites !== undefined) {
lastInvites.totalSend++
lastInvites.lastSend = now
if (lastInvites.totalSend > 5 && now - lastInvites.lastSend < 60 * 1000) {
// Less 60 seconds between invites
throw new PlatformError(
new Status(Severity.ERROR, platform.status.WorkspaceRateLimit, { workspace: workspaceName })
)
}
invitesSend.delete(email)
} else {
invitesSend.set(email, {
lastSend: now,
totalSend: 1
})
}
// We need to cleanup map
for (const [k, vv] of invitesSend.entries()) {
if (vv.lastSend < now - 60 * 1000) {
invitesSend.delete(k)
}
}
}View on GitHub (pinned to 63e28dc964)
Solutions
- Wait at least 60 seconds before resending an invite to the same email.
- Add exponential backoff / jitter to any automated retry loop around resendInvite or createWorkspace.
- Debounce the resend action in the UI and disable the button after the first click.
- Check the invite-send counter before calling: if an invite was recently sent, surface 'invite already sent' instead of calling again.
- If the counter is stale from testing, restart the service (invitesSend is in-memory) and re-test with delays.
Example fix
// before
for (let i = 0; i < 10; i++) await ops.resendInvite(ctx, token, params)
// after
await backoffRetry(() => ops.resendInvite(ctx, token, params), { minDelayMs: 60_000, maxAttempts: 6 }) Defensive patterns
Strategy: retry
Validate before calling
if (lastSentAt[email] && Date.now() - lastSentAt[email] < 60_000 && sendCount[email] > 5) {
throw new Error('invite throttle: wait before resending to ' + email)
} Try / catch
try {
await ops.resendInvite(ctx, token, params)
} catch (e) {
if (isStatus(e, platform.status.WorkspaceRateLimit)) {
await sleep(60_000)
return retryWithBackoff(() => ops.resendInvite(ctx, token, params))
}
throw e
} Prevention
- Debounce resend buttons and disable them for 60 seconds after each send.
- Implement exponential backoff with jitter on automated retries.
- Track per-email send timestamps client-side and refuse to exceed 5 sends/minute.
When it happens
Trigger: Calling resendInvite (or createWorkspace) for the same email more than 5 times within a 60-second window; retry loops on resendInvite that do not back off; multiple concurrent invites to one address from different callers.
Common situations: An automated retry script hammering resendInvite on transient mail failures; a user double-clicking a resend button repeatedly; load tests against the invite endpoint; shared service accounts inviting the same address from several jobs.
Related errors
AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29).
Data as JSON: /api/errors/8dce6a07b2168fb9.
Report an issue: GitHub.