/** * apps/worker — per-workspace automation email daily cap (TASK-0112 §8). * * Redis counter `auto:email:{wsId}:{utc-date}` (shared `emailCapKey`). The * SEND_EMAIL executor INCRs before enqueueing; if the result exceeds the cap * (AUTOMATION_EMAIL_DAILY_CAP = 500) the email is NOT sent and the executor * throws the FATAL EMAIL_DAILY_CAP (no retry — the quota won't change by * retrying). The counter has a 24 h TTL so it rolls over on its own. */ import { AUTOMATION_EMAIL_DAILY_CAP, emailCapKey } from "@factory/shared"; import type { Redis } from "ioredis"; import { FatalError } from "./classify.js"; /** * Reserve one email slot for the workspace today. Throws FatalError * EMAIL_DAILY_CAP when the cap is exceeded. Returns the new counter value. */ export async function reserveEmailSlot(redis: Redis, workspaceId: string): Promise { const key = emailCapKey(workspaceId); const n = await redis.incr(key); if (n === 1) { // First send today: arm the 24 h TTL. await redis.expire(key, 24 * 60 * 60); } if (n > AUTOMATION_EMAIL_DAILY_CAP) { throw new FatalError("EMAIL_DAILY_CAP", `workspace ${workspaceId} exceeded ${AUTOMATION_EMAIL_DAILY_CAP} automation emails today`); } return n; }