Programaciones
Un Schedule describe qué plantilla enviarle a qué número, y cuándo.
Puntual vs. recurrente
Sección titulada «Puntual vs. recurrente»// Puntual: se envía una vez en sendAt{ "templateId": "...", "phone": "...", "variables": {}, "sendAt": "2026-01-01T15:00:00Z" }
// Recurrente: se recalcula nextRunAt tras cada envío según cronExpression{ "templateId": "...", "phone": "...", "variables": {}, "recurring": true, "cronExpression": "0 9 * * 1", "timezone": "America/Lima" }En modo recurrente, nextRunAt se recalcula con CronTime de la librería cron después de cada envío exitoso — no hay que recrear el schedule.
Estados
Sección titulada «Estados»| Estado | Significado |
|---|---|
pending |
Esperando su sendAt (o nextRunAt si es recurrente). |
queued |
Ya recogido por el cron y esperando turno en la cola de envío de su sesión. |
sent |
Enviado con éxito (solo aplica a no-recurrentes; los recurrentes vuelven a active/pending según corresponda). |
failed |
Falló y agotó reintentos. |
cancelled |
Cancelado manualmente (PATCH /:id/cancel). |
active |
Recurrente, esperando su próxima ejecución. |
Cómo procesa el scheduler
Sección titulada «Cómo procesa el scheduler»Un cron corre cada minuto (SchedulerProcessor.processPendingSchedules) sobre todas las sesiones conectadas:
- Busca schedules
pending(oactiverecurrentes) con su hora vencida. - Los marca
queuedde inmediato — así el próximo tick del cron no los vuelve a recoger mientras esperan turno. - Los encola en
SendQueueService, que aplica el delay/jitter/cuota/validaciones anti-baneo antes de mandar de verdad (ver Envío masivo seguro).
Si el proceso se reinicia con schedules en queued (porque la cola vive en memoria), al arrancar se resetean automáticamente a pending para que no queden colgados.
Reintentos
Sección titulada «Reintentos»Un envío fallido reintenta hasta 3 veces (MAX_RETRIES), incrementando retryCount y volviendo a pending. Al agotar los reintentos pasa a failed con el error guardado.
Si el motivo del rechazo es una cuota o pausa anti-baneo (no un error real), no cuenta como intento fallido: el schedule se reprograma a pending con un nuevo sendAt calculado según el motivo (fin de cuota, fin de quiet hours, etc).