> ## Documentation Index
> Fetch the complete documentation index at: https://internal.softcrum.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Estándar — Jobs, colas y workers (v2)

> Interfaz en packages/core: publish(queue, payload, opts) y schedule(queue, payload, runAt). Los payloads son envelopes validados con Zod: job_id uuidv7, tenant_id, correlation_id, kind y data.

> Traducción. Autoritativo: [`../../standards/jobs.md`](/standards/jobs).

## QueuePort (categoría 7 de la constitución)

* Interfaz en `packages/core`: `publish(queue, payload, opts)`, `schedule(queue, payload, runAt)`.
  Los payloads son envelopes validados con Zod: `{ job_id: uuidv7, tenant_id, correlation_id, kind, data }`.
* Binding en el composition root de cada módulo (R21). Adaptador por defecto en F1: Vercel Queues.
  Vendors nuevos (QStash, etc.): ADR antes del primer import.

## Consumidores

* Idempotencia OBLIGATORIA: verificar e insertar en `core.processed_jobs (job_id, consumer,
  processed_at)` dentro de la transacción de trabajo; un duplicado hace ack y salta. Job de limpieza
  por TTL.
* Política de reintentos (default de industria, sobreescribible por cola con justificación en el
  spec del módulo): 5 intentos, backoff exponencial con jitter (base 30 s, tope 1 h).
* Los reintentos agotados y las fallas duras DEBEN persistirse en `core.dead_letters (job_id, queue,
  payload, attempts, last_error, status: pending_review|replayed|discarded, correlation_id)` más
  alerta a BetterStack. La herramienta de replay vive en Softcrum Ops. Los éxitos de alto volumen
  son métricas, no filas.
* Mensajes envenenados: los payloads inválidos de schema van directo a dead\_letters (sin reintentos).

## Colas de notificación (junto con standards/notifications.md)

* Una cola por canal por carril: `notif.{canal}.transactional` y `notif.{canal}.marketing`.
* El transaccional SIEMPRE tiene prioridad: los workers de marketing ceden (pausan o drenan) cuando
  la profundidad transaccional supera el umbral.
* Rate limiting: token bucket en Upstash por PROVEEDOR (global — por ejemplo, los 10 rps de Resend
  son por team, a través de TODOS los tenants) y por TENANT (uso justo). Un 429 implica backoff,
  nunca descartar.
* Blasts de email: exclusivamente el endpoint batch de Resend (100 por request); NUNCA sincronizar
  contactos a Resend Audiences (nuestro almacén es la fuente de verdad).

## Cron fan-out

* Patrón de fan-out por tenant (existente) para: pre-creación de particiones, exportación y purga de
  retención, reconciliación nocturna, recomputes de frescura de datos (tier por entitlement:
  nocturno/12h/6h/3h/1h), escaneos de points\_expiring\_soon, snapshots de consumo (cada 30 min).
