Appearance
Backend & Worker
API REST (Node.js/Express)
Endpoints Obligatorios
1. Crear Job de Envío
- Ruta:
POST /api/jobs - Payload:
json
{
"subject": "Asunto del email",
"body": "Contenido HTML/Text",
"recipients": ["email1@example.com", "email2@example.com"]
}Validaciones:
subject: Requerido, string no vacío, máximo 200 caracteresbody: Requerido, string no vacíorecipients: Array requerido, mínimo 1 email, máximo 1000, formato email válido
Proceso:
- Crear registro en tabla
email_jobs(status: pending) - Encolar en Bull/Redis únicamente el
jobUuid - Retornar inmediatamente sin esperar procesamiento
- Crear registro en tabla
Respuesta:
201 Createdcon{ jobId, status, createdAt }Errores:
400 Bad Requestpara validaciones fallidas413 Payload Too Largesi recipients > 1000
2. Obtener Estado del Job
- Ruta:
GET /api/jobs/:jobId - Respuesta:
json
{
"id": "uuid",
"status": "pending|processing|completed|failed",
"progress": { "sent": 45, "failed": 2, "total": 50 },
"createdAt": "timestamp",
"startedAt": "timestamp",
"completedAt": "timestamp",
"error": "mensaje de error si aplica"
}3. Listar Jobs
- Ruta:
GET /api/jobs - Query Params:
status: Filtro por estado (pending, processing, completed, failed)page: Número de página (default: 1)pageSize: Items por página (default: 20, max: 100)sortBy: Campo de ordenamiento (createdAt, completedAt)order: asc/desc (default: desc)
- Respuesta:
json
{
"data": [...],
"pagination": {
"page": 1,
"pageSize": 20,
"total": 150,
"totalPages": 8
}
}4. Reintentar Job Fallido
- Ruta:
POST /api/jobs/:jobId/retry - Condición: Solo jobs con status
failed - Proceso: Resetea contadores, cambia status a pending, re-encola
- Respuesta:
200 OKcon el job actualizado
5. Cancelar Job
- Ruta:
DELETE /api/jobs/:jobId - Condición: Solo jobs
pendingoprocessing - Proceso: Marca como
cancelled, remueve de cola Redis - Respuesta:
200 OK
6. Obtener Logs de un Job
- Ruta:
GET /api/jobs/:jobId/logs - Respuesta: Array de intentos con timestamps, errores, emails procesados
Buenas Prácticas Backend
- Separación de Capas: Controllers → Services → Repositories
- Validación: Uso de middleware de validación (express-validator)
- Manejo de Errores: Middleware centralizado de errores
- Códigos HTTP: 200, 201, 400, 404, 409, 413, 500 apropiadamente
- Transacciones: Uso de transacciones al crear jobs + encolar
- Logging: Winston/Pino con niveles apropiados (info, error, debug)
Worker (Node.js + Bull)
1. Configuración y Consumo de Colas
- Cola: Debe conectarse a la cola llamada
email-queue. - Payload: El worker recibirá EXCLUSIVAMENTE un objeto
{ jobUuid: string }. - Concurrencia: Debe procesar
Ntrabajos simultáneamente, dondeNestá definido por la variable de entornoWORKER_CONCURRENCY(Default: 5). - Librería: Se debe utilizar
bull(obullmq), configurando la conexión a Redis apropiadamente.
2. Lógica de Procesamiento (The Processor)
El worker ejecutará la siguiente lógica para cada trabajo recibido:
Recuperación de Estado:
- Usar
jobUuidpara buscar el registro en la tablaEmailJobusando Prisma. - Si el job no existe o su estado es
CANCELLED, terminar el proceso inmediatamente sin error. - Actualizar el estado del
EmailJobaPROCESSINGy setearstartedAt = now().
- Usar
Iteración de Envíos:
- Iterar sobre la lista de
recipients. - Por cada destinatario:
- Verificar si ya existe un log de éxito para este email en este job (idempotencia). Si existe, saltar.
- Intentar envío usando
nodemailer. - Éxito: Crear registro en
EmailLogcon statusSUCCESS, incrementarsentEmailsenEmailJob. - Fallo: Crear registro en
EmailLogcon statusFAILEDy guardar el mensaje de error. IncrementarfailedEmails.
- Control de Flujo (Rate Limiting Interno): Esperar
Xmilisegundos entre cada envío, definido porEMAIL_BATCH_DELAY(ej: 100ms).
- Iterar sobre la lista de
Actualización de Progreso:
- Cada 10 envíos (o porcentaje equivalente), actualizar el objeto
job.progress(percentage)en Bull para monitoreo externo. - Opcionalmente, actualizar contadores parciales en la base de datos si se requiere persistencia estricta en tiempo real.
- Cada 10 envíos (o porcentaje equivalente), actualizar el objeto
Finalización de Trabajo:
- Al terminar todos los recipients, evaluar:
- Si
failedEmails> 50% del total: MarcarEmailJobcomoFAILED. - Caso contrario: Marcar
EmailJobcomoCOMPLETED.
- Si
- Setear
completedAt = now().
- Al terminar todos los recipients, evaluar:
3. Gestión de Errores y Reintentos (Job Level)
Si ocurre un error CRÍTICO durante el procesamiento (ej: caída de DB, error de conexión SMTP general):
- Estrategia de Reintento (Backoff): Configurar las opciones del Job en Bull para usar backoff exponencial:
- Delay base: 1 minuto.
- Factor: 2 (1m, 2m, 4m...).
- Máximo de intentos: 4.
- Fallo Definitivo: Si se agotan los intentos, el procesador debe capturar el evento
failedde Bull y actualizar el registroEmailJoba statusFAILEDcon el mensaje de error sistémico en el campoerror.
Arquitectura de Datos
Tablas MySQL (Prisma Schema)
email_jobs
prisma
model EmailJob {
id String @id @default(uuid()) @db.Uuid
subject String @db.VarChar(200)
body String @db.Text
recipients Json // Array de emails
status JobStatus @default(PENDING)
totalEmails Int
sentEmails Int @default(0)
failedEmails Int @default(0)
createdAt DateTime @default(now())
startedAt DateTime?
completedAt DateTime?
error String? @db.Text
logs EmailLog[]
}
enum JobStatus {
PENDING
PROCESSING
COMPLETED
FAILED
CANCELLED
}email_logs
prisma
model EmailLog {
id String @id @default(uuid()) @db.Uuid
jobId String @db.Uuid
email String @db.VarChar(255)
status LogStatus
attempt Int @default(1)
sentAt DateTime?
error String? @db.Text
createdAt DateTime @default(now())
job EmailJob @relation(fields: [jobId], references: [id])
@@index([jobId])
@@index([status])
}
enum LogStatus {
SUCCESS
FAILED
PENDING
}Redis (Bull Queue)
- Estructura de Job en Redis:
json
{
"jobUuid": "550e8400-e29b-41d4-a716-446655440000"
}- Configuración de Cola:
- Queue name:
email-queue - Concurrency: 5 workers
- Max attempts: 4
- Backoff: exponential (1min, 5min, 15min)
- Remove on complete: false (mantener para auditoría)
- Remove on fail: false
- Queue name:
