Skip to content

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 caracteres
    • body: Requerido, string no vacío
    • recipients: Array requerido, mínimo 1 email, máximo 1000, formato email válido
  • Proceso:

    1. Crear registro en tabla email_jobs (status: pending)
    2. Encolar en Bull/Redis únicamente el jobUuid
    3. Retornar inmediatamente sin esperar procesamiento
  • Respuesta: 201 Created con { jobId, status, createdAt }

  • Errores:

    • 400 Bad Request para validaciones fallidas
    • 413 Payload Too Large si 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 OK con el job actualizado

5. Cancelar Job

  • Ruta: DELETE /api/jobs/:jobId
  • Condición: Solo jobs pending o processing
  • 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 N trabajos simultáneamente, donde N está definido por la variable de entorno WORKER_CONCURRENCY (Default: 5).
  • Librería: Se debe utilizar bull (o bullmq), 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:

  1. Recuperación de Estado:

    • Usar jobUuid para buscar el registro en la tabla EmailJob usando Prisma.
    • Si el job no existe o su estado es CANCELLED, terminar el proceso inmediatamente sin error.
    • Actualizar el estado del EmailJob a PROCESSING y setear startedAt = now().
  2. 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 EmailLog con status SUCCESS, incrementar sentEmails en EmailJob.
      • Fallo: Crear registro en EmailLog con status FAILED y guardar el mensaje de error. Incrementar failedEmails.
    • Control de Flujo (Rate Limiting Interno): Esperar X milisegundos entre cada envío, definido por EMAIL_BATCH_DELAY (ej: 100ms).
  3. 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.
  4. Finalización de Trabajo:

    • Al terminar todos los recipients, evaluar:
      • Si failedEmails > 50% del total: Marcar EmailJob como FAILED.
      • Caso contrario: Marcar EmailJob como COMPLETED.
    • Setear completedAt = now().

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 failed de Bull y actualizar el registro EmailJob a status FAILED con el mensaje de error sistémico en el campo error.

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