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

  1. Wait at least 60 seconds before resending an invite to the same email.
  2. Add exponential backoff / jitter to any automated retry loop around resendInvite or createWorkspace.
  3. Debounce the resend action in the UI and disable the button after the first click.
  4. Check the invite-send counter before calling: if an invite was recently sent, surface 'invite already sent' instead of calling again.
  5. 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

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.