# DeskcommCRM External Rate Limiting Using Upstash Redis: Implementation and Configuration Guide

> Implement external rate limiting for DeskcommCRM with Upstash Redis. This guide details the fixed-window counter and fallback to in-memory storage.

- Repository: [Rafael Melgaço/DeskcommCRM](https://github.com/melgarafael/DeskcommCRM)
- Tags: how-to-guide
- Published: 2026-09-12

---

**Yes, DeskcommCRM supports external rate limiting using Upstash Redis through a per-tenant fixed-window counter implementation that automatically falls back to in-memory storage when the external store is unavailable.**

DeskcommCRM implements robust **per-tenant rate limiting** for AI dispatches via an external Upstash Redis store defined in [`lib/ai/dispatcher/rate-limit.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/ai/dispatcher/rate-limit.ts). The architecture guarantees that throttling constraints persist across serverless invocations while maintaining operational continuity during network partitions through intelligent degradation strategies.

## Environment Configuration and Validation

External rate limiting requires specific environment variables that the application validates before establishing connections.

### Required Environment Variables

According to the DeskcommCRM source code, the system expects two critical variables defined in [`lib/env.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/env.ts):

- `UPSTASH_REDIS_REST_URL`
- `UPSTASH_REDIS_REST_TOKEN`

These credentials are validated by [`lib/redis-config.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/redis-config.ts) to ensure proper formatting before the Redis client is instantiated. The application will abort startup if these values are missing or malformed, preventing silent fallback to unprotected operation.

### Connection Security Settings

When initializing the client in [`lib/ai/dispatcher/rate-limit.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/ai/dispatcher/rate-limit.ts), the code explicitly disables automatic retries:

```typescript
new Redis({ url, token, retry: false })

```

This configuration ensures that rate-limiting decisions fail fast during network partitions rather than hanging indefinitely on stale connections, allowing the system to trigger the in-memory fallback immediately.

## Core Rate Limiting Logic in rate-limit.ts

The primary implementation resides in [`lib/ai/dispatcher/rate-limit.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/ai/dispatcher/rate-limit.ts), which exports the `checkRateLimit` and `peekRateLimit` functions used throughout the AI dispatch pipeline.

### Fixed-Window Counter Algorithm

DeskcommCRM employs a **fixed-window counter** strategy that partitions time into discrete windows. For each request, the system constructs a composite key using the pattern `${bucket}:${windowStart}`, where `bucket` identifies the tenant/resource and `windowStart` represents the Unix timestamp of the current window boundary.

The algorithm executes atomic Redis operations in sequence:

1. `redis.incr(key)` increments the counter
2. On first increment (`count === 1`), `redis.expire(key, windowSec)` sets the TTL

This approach ensures that Redis handles both the counting and expiration atomically, preventing race conditions in high-concurrency scenarios.

### Tenant Isolation Architecture

Each organization receives isolated rate-limiting buckets (for example, `ai-runs:org123:42`), ensuring that heavy usage by one tenant does not exhaust the quota of another. The `bucket` parameter passed to `checkRateLimit` typically includes the tenant identifier, maintaining strict separation between organizations in multi-tenant CRM deployments.

## Resilience and Fallback Behavior

When the Upstash Redis endpoint is unreachable, DeskcommCRM does not bypass rate limiting entirely.

### In-Memory Degradation Mechanism

The `checkRateLimit` function maintains a private `_memBuckets` Map that shadows the Redis state. If the `redis.incr` call throws an exception—whether from network timeout or authentication failure—the code catches the error, logs a warning, and transitions to the in-memory counter stored in `_memBuckets`. While this makes the limit **per-process** rather than globally shared across instances, it preserves protective throttling during external outages.

### Non-Destructive Limit Inspection

The `peekRateLimit` function (lines 63-73 in [`rate-limit.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/rate-limit.ts)) retrieves the current counter value without incrementing it. This supports "check-before-act" flows such as login attempt validation, where you want to assess remaining quota before consuming a request. The function attempts to read from Redis first, then falls back to the in-memory cache if the external store is unavailable.

## Practical Implementation Examples

### Enforcing AI Dispatch Limits

Use `checkRateLimit` to protect AI-intensive operations with configurable windows:

```typescript
import { checkRateLimit } from '@/lib/ai/dispatcher/rate-limit';

async function dispatchAIRequest(orgId: string) {
  const bucket = `ai-runs:${orgId}`;
  const limit = 60;          // Maximum 60 calls
  const windowSec = 60;      // Per 60-second window

  const { allowed, count } = await checkRateLimit(bucket, limit, windowSec);
  if (!allowed) {
    throw new Error(`Rate limit exceeded – ${count}/${limit} in the last minute`);
  }
  // Proceed with AI dispatch...
}

```

### Peeking Login Attempt Counters

Use `peekRateLimit` for authentication throttling where you must check status before consuming a quota:

```typescript
import { peekRateLimit } from '@/lib/ai/dispatcher/rate-limit';

async function canAttemptLogin(orgId: string) {
  const bucket = `login:${orgId}`;
  const windowSec = 300; // 5-minute window
  const current = await peekRateLimit(bucket, windowSec);
  return current < 5; // Allow up to 5 attempts in the window
}

```

### Environment Configuration

Ensure your deployment includes these variables in `.env` or `.env.local`:

```dotenv
UPSTASH_REDIS_REST_URL=https://your-redis-instance.upstash.io
UPSTASH_REDIS_REST_TOKEN=your-secret-token

```

## Summary

- **DeskcommCRM supports external rate limiting using Upstash Redis** through the `@upstash/redis` package (version `^1.38.3` as declared in [`package.json`](https://github.com/melgarafael/DeskcommCRM/blob/main/package.json))
- Configuration relies on `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` environment variables validated in [`lib/redis-config.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/redis-config.ts)
- The `checkRateLimit` function in [`lib/ai/dispatcher/rate-limit.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/ai/dispatcher/rate-limit.ts) implements a fixed-window counter with automatic Redis expiry using `incr` and `expire` commands
- If Redis connectivity fails, the system falls back to an in-memory `_memBuckets` Map to maintain per-process protection while logging warnings
- The `peekRateLimit` function enables non-destructive quota inspection for sensitive operations like authentication without consuming the rate limit budget

## Frequently Asked Questions

### What happens if Upstash Redis is temporarily unavailable?

DeskcommCRM detects connection failures in `checkRateLimit` and automatically switches to an in-memory fallback stored in the `_memBuckets` Map. While this limits the constraint to individual process instances rather than the global cluster, rate limiting continues to protect the system. Operators receive warning logs indicating the degradation mode is active.

### How does DeskcommCRM prevent rate-limit key collisions between tenants?

The implementation constructs unique keys using the pattern `${bucket}:${windowStart}`, where the bucket identifier typically includes the tenant ID (for example, `ai-runs:org123`). This ensures that counters for different organizations remain strictly isolated in both Redis and the in-memory fallback, preventing cross-tenant quota bleeding.

### Can I check remaining quota without consuming a request?

Yes. The `peekRateLimit` function in [`lib/ai/dispatcher/rate-limit.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/ai/dispatcher/rate-limit.ts) reads the current counter value from Redis or the in-memory cache without calling `incr`. This is ideal for pre-flight checks on expensive operations or login attempt validation where you must verify availability before deducting from the limit.

### Which version of the @upstash/redis SDK does DeskcommCRM use?

The [`package.json`](https://github.com/melgarafael/DeskcommCRM/blob/main/package.json) specifies `@upstash/redis` version `^1.38.3`. The client is instantiated in [`lib/ai/dispatcher/rate-limit.ts`](https://github.com/melgarafael/DeskcommCRM/blob/main/lib/ai/dispatcher/rate-limit.ts) with `retry: false` to ensure immediate feedback during network partitions rather than hanging on retry loops.