Repository navigation
systems email and rate limiting
Active contributors: ferdiiskandar
Two related safeguards live here. The email queue delivers transactional mail without blocking the request that triggered it, retrying transient failures and pruning old rows. The rate-limit layer throttles abuse-sensitive auth actions by IP and by email using shared counters, so repeated registration, password-reset, or verification-resend attempts are capped.
lib/
email/queue.ts welcome-email queue
email/welcome.ts Resend delivery and HTML
auth/shared-rate-limit.ts fixed-window counter store
auth/abuse-protection.ts per-action IP and email rules
| Symbol | Kind | Path |
|---|---|---|
enqueueWelcomeEmail, processEmailQueue
|
queue functions | lib/email/queue.ts |
EmailQueueProcessingResult |
result type | lib/email/queue.ts |
sendWelcomeEmail |
Resend delivery | lib/email/welcome.ts |
consumeSharedRateLimit, SharedRateLimitResult
|
counter functions | lib/auth/shared-rate-limit.ts |
enforceAuthAbuseProtection |
auth rules | lib/auth/abuse-protection.ts |
enqueueWelcomeEmail upserts an EmailJob keyed by the idempotency key welcome:<userId>, so re-running registration for the same user refreshes the row instead of creating a duplicate. Each job starts PENDING with maxAttempts 5 and nextAttemptAt set to now.
processEmailQueue returns an empty summary when RESEND_API_KEY is missing, otherwise loads due jobs (PENDING or RETRYING with nextAttemptAt <= now, oldest first, limited by limit). For each job it claims with a conditional updateMany to PROCESSING that increments attempts; if the claim updates zero rows, the job is skipped, which protects against two workers grabbing one job. Delivery dispatches on type, and the WELCOME branch parses the payload with Zod before calling sendWelcomeEmail. Success sets SENT and sentAt. Failure retries while attemptNumber < maxAttempts, using exponential backoff clamped between one minute and fifteen minutes, and marks FAILED once attempts are exhausted. maybePruneFinishedJobs runs on a 2 percent chance per pass and deletes SENT or FAILED rows whose updatedAt is older than 14 days.
sendWelcomeEmail skips silently when RESEND_API_KEY is unset, sends through Resend from RESEND_FROM_EMAIL, and sets List-Unsubscribe and List-Unsubscribe-Post headers pointing at the app unsubscribe URL.
graph TD
Register[Registration flow] --> Enqueue[enqueueWelcomeEmail]
Enqueue --> Job[(email_jobs)]
Cron[Queue worker] --> Process[processEmailQueue]
Process --> Claim[conditional claim to PROCESSING]
Claim --> Deliver[sendWelcomeEmail via Resend]
Deliver -->|success| Sent[status SENT]
Deliver -->|error under max| Retry[status RETRYING with backoff]
Deliver -->|error at max| Fail[status FAILED]
Process --> Prune[prune SENT/FAILED older than 14 days]
Request[Auth route] --> Abuse[enforceAuthAbuseProtection]
Abuse --> IP[consumeSharedRateLimit scope IP]
Abuse --> Email[consumeSharedRateLimit scope EMAIL]
IP --> Counter[(rate_limit_counters)]
Email --> Counter
consumeSharedRateLimit hashes the raw key with SHA-256 and computes a fixed window by flooring the timestamp to the window size. It inserts a counter with count 1 on first use, and on a unique-constraint conflict (Prisma P2002, meaning a concurrent insert) it retries recursively. When the stored count has reached the rule limit it returns allowed: false with retryAfterSeconds until windowEnd; otherwise it increments. maybePruneExpiredCounters runs on a 2 percent chance and deletes counters whose windowEnd is more than 24 hours old.
enforceAuthAbuseProtection applies the IP rule first, then the email rule, and returns as soon as either fails. Rules are REGISTER at 5 per 15 minutes by IP and 3 per hour by email, FORGOT_PASSWORD at the same pair, and RESEND_VERIFICATION at 6 per 15 minutes by IP and 3 per 30 minutes by email. The client IP comes from the first x-forwarded-for entry, then x-real-ip, then unknown; the email key is trimmed and lowercased.
The queue is triggered after a new Prisma user is created, while enforceAuthAbuseProtection guards the registration, password-reset, and verification-resend routes. Both persist to tables described in Data and Prisma. The Resend and app-URL variables are listed in Configuration, and outbound mail handling is covered in Security.
- Change retry backoff, attempt cap, or prune policy:
lib/email/queue.ts. - Change sender, subject, or unsubscribe headers:
lib/email/welcome.ts. - Change window hashing or prune policy:
lib/auth/shared-rate-limit.ts. - Change per-action limits: the
RULESmap inlib/auth/abuse-protection.ts.
| File | What it holds |
|---|---|
lib/email/queue.ts |
Idempotent enqueue, claim, deliver, retry, fail, prune |
lib/email/welcome.ts |
Welcome HTML and Resend send with unsubscribe headers |
lib/auth/shared-rate-limit.ts |
SHA-256 keyed fixed-window counters |
lib/auth/abuse-protection.ts |
REGISTER, FORGOT_PASSWORD, RESEND_VERIFICATION rules |
Open-source prompt engineering and multi-LLM tooling by Sentra Artificial Intelligence.
MyPrompt develops practical approaches to prompt engineering, multi-LLM optimisation, reusable prompt systems, and AI-native workflows — with an emphasis on structured, interoperable, and real-world AI use.
Built in Indonesia as part of the Sentra Artificial Intelligence ecosystem.
Sentra Artificial Intelligence · Source Repository · Official Website
Dr Ferdi Iskandar — Creator & Maintainer
LinkedIn ·
ORCID ·
Hugging Face ·
Kaggle ·
Medium ·
Substack ·
X ·
Threads
MyPrompt · Sentra Artificial Intelligence · Indonesia