23 años desde Estocolmo
Notas del operador · Desde Estocolmo

Diseño de workflows transaccionales event-driven — la arquitectura cuando el envío de email se modela alrededor de eventos del producto en lugar de fechas o cron jobs

Por Wilfred Teague · Publicado el 24 de marzo de 2025 · Lectura aproximada 6 min

El modelo tradicional de workflows transaccionales: cron job que corre cada 30 minutos, consulta la base de datos para encontrar usuarios que cumplen criterio (registrados hace 7 días, sin haber hecho onboarding completo), y envía emails al lote. Funciona pero tiene problemas — latencia, condiciones de carrera, dificultad de cambiar el workflow sin tocar código. El modelo event-driven es alternativa más limpia: el sistema reacciona a eventos del producto en tiempo real, los workflows se modelan como reacciones a esos eventos. Este post cubre cómo se diseña este modelo cuando el envío de email se integra con producto event-driven.

El problema con cron-based workflows

Workflow típico cron-based para "enviar email de onboarding 7 días después del registro":

# Cron job runs every hour
SELECT user_id, email FROM users
WHERE created_at <= NOW() - INTERVAL '7 days'
  AND created_at > NOW() - INTERVAL '7 days 1 hour'
  AND email_onboarding_d7_sent_at IS NULL
  AND status = 'active';

# For each: send email, mark email_onboarding_d7_sent_at = NOW()

Problemas operativos:

Latencia hasta 1 hora. Si el usuario se registra a las 9:15 AM, el email del día 7 podría enviarse hasta a las 10:14 AM del día 7 — casi una hora después del momento exacto.

Condiciones de carrera. Si el cron tarda más de lo esperado y el siguiente arranca antes de que termine, posible duplicación. Si la query falla a mitad del lote, recuperación es complicada.

Difícil de cambiar. Cambiar el workflow ("ahora enviar a día 5 en lugar de día 7") requiere modificar query, considerar usuarios actualmente en window, decidir qué hacer con los que ya recibirían y los que no.

Difícil de extender. Añadir nuevo email al workflow ("también enviar a día 14 si no han activado feature X") significa nueva query, nuevo cron, nuevas condiciones. Cada nuevo step añade complejidad.

Visibilidad limitada. ¿Cuántos usuarios están "en el día 6" del onboarding? ¿Cuántos completaron antes de día 7? El estado está implícito en queries, no explícito en datos.

Modelo event-driven — la alternativa

El modelo event-driven invierte la lógica. En lugar de polling periódico, el sistema reacciona a eventos del producto en tiempo real:

Cuando el usuario se registra, el producto publica evento user.registered al event bus. Un consumer escucha eventos relevantes para email workflows. Cuando recibe user.registered, encola un trabajo "enviar onboarding_day_7 email a este usuario en exactamente 7 días". El sistema de scheduling (Temporal, AWS EventBridge Scheduler, Hangfire, Sidekiq scheduled jobs) ejecuta el trabajo en el momento exacto.

Beneficios operativos:

Latencia exacta. El email se envía 7 días desde el momento del registro, no hasta 1 hora después.

Sin condiciones de carrera. Cada evento se procesa una sola vez. Idempotency keys garantizan que reintentos no dupliquen.

Fácil de cambiar el workflow. Cambiar a día 5 modifica el delay del trabajo. Para usuarios actualmente en flow, decisión explícita: cancelar pendings y reschedular, o dejar que terminen el actual.

Fácil de extender. Añadir nuevo email significa añadir nuevo evento a escuchar y nuevo handler. Sin tocar lógica existente.

Visibilidad explícita. La cola de trabajos pendientes es estado observable. ¿Cuántos onboardings activos? Query directa al sistema de scheduling.

Componentes del stack event-driven

Stack típico para implementar este pattern:

Event bus / message broker. Donde se publican y consumen eventos. Opciones: Apache Kafka (alto volumen, alta garantía), AWS EventBridge (serverless, integración nativa con AWS), Google Cloud Pub/Sub, RabbitMQ (estándar maduro), Redis Streams (simple para casos pequeños).

Workflow engine / scheduler. Donde se programan y ejecutan trabajos. Opciones: Temporal (durable execution con código en lenguaje familiar), AWS Step Functions (state machine declarativo), Hangfire (.NET background jobs), Sidekiq (Ruby), Celery (Python), Bull/BullMQ (Node.js).

Email API client. SDK del proveedor de email API REST (cubierto en post separado). El handler del workflow llama al cliente para enviar.

Storage para estado del workflow. Base de datos que rastrea estado de cada workflow activo: qué emails se han enviado, qué eventos han ocurrido, qué está pendiente. Permite debugging y reporting.

Observabilidad. Tracing distribuido (OpenTelemetry) para seguir el evento desde origen hasta envío de email. Logging estructurado. Métricas (Prometheus + Grafana).

Ejemplo concreto — onboarding workflow

Workflow de onboarding típico modelado event-driven:

# Cuando user.registered ocurre
on_event("user.registered", payload):
    # Inmediato: welcome email
    schedule_email(
        template="welcome",
        recipient=payload.email,
        delay=0,  # Send now
        idempotency_key=f"welcome-{payload.user_id}"
    )
    
    # Día 1: getting started guide
    schedule_email(
        template="getting_started",
        recipient=payload.email,
        delay="1 day",
        idempotency_key=f"day1-{payload.user_id}",
        cancel_if_event="user.completed_onboarding"
    )
    
    # Día 3: tips and tricks (solo si no han activado feature core)
    schedule_email(
        template="tips",
        recipient=payload.email,
        delay="3 days",
        idempotency_key=f"day3-{payload.user_id}",
        condition=lambda: not user_has_activated_core_feature(payload.user_id)
    )
    
    # Día 7: case study email
    schedule_email(
        template="case_study",
        recipient=payload.email,
        delay="7 days",
        idempotency_key=f"day7-{payload.user_id}"
    )

# Cuando user.completed_onboarding ocurre (cancela future emails)
on_event("user.completed_onboarding", payload):
    cancel_scheduled_emails_for(payload.user_id, except_completed=True)
    
    # Trigger nueva sequence
    schedule_email(
        template="onboarding_complete",
        recipient=payload.email,
        delay=0,
        idempotency_key=f"complete-{payload.user_id}"
    )

Características clave del modelo:

Idempotency keys explícitas. Cada envío tiene key única basada en user_id y tipo de email. Si el handler se ejecuta dos veces (retry, reentrega del evento), no duplica.

Cancellation conditions. cancel_if_event permite cancelar emails programados si ocurre evento que los hace innecesarios. Si el usuario completa onboarding al día 4, los emails de día 7 y 14 se cancelan automáticamente.

Conditions evaluadas en tiempo de envío. condition lambda evaluada justo antes de enviar. Permite lógica como "solo enviar tips si no ha activado feature core" — el sistema verifica al momento, no al programar.

Workflows por tipo de evento separados. Cada handler es función pequeña con scope claro. Cambios en uno no afectan al otro. Testing es trivial.

Idempotency end-to-end

Idempotency es crítica en sistemas event-driven porque eventos pueden entregarse múltiples veces (at-least-once delivery es default en la mayoría de event buses). Sin idempotency robusta, el mismo email puede enviarse varias veces.

Stack completo de idempotency:

Capa 1 — Event bus. Algunos brokers (Kafka con consumer groups + offset management) garantizan exactly-once delivery. Otros (RabbitMQ, SQS) garantizan at-least-once — el handler debe ser idempotente.

Capa 2 — Workflow scheduler. Trabajo programado debería tener key única. Si el mismo trabajo se programa dos veces (procesamiento duplicado del evento), el scheduler detecta duplicado y deduplicar.

Capa 3 — Email API call. Idempotency key del proveedor de email (X-Idempotency-Key en SendGrid, equivalente en otros) garantiza que llamadas duplicadas al API no resultan en envíos duplicados.

Las tres capas trabajando juntas garantizan que cada email se envía exactamente una vez, independientemente de cuántas veces el evento original se procese o el trabajo se reintente.

Observabilidad end-to-end

La complejidad del sistema event-driven hace observabilidad crítica. Sin ella, debugging es ejercicio de inferencia.

Tracing distribuido. OpenTelemetry o equivalente. Cada evento entra al sistema con trace_id. El trace propaga a través de event bus, workflow scheduler, email API call, webhook recibido. Resultado: ver el path completo de un evento desde "user.registered" hasta "delivered" en un solo dashboard.

Logging estructurado. Cada step loguea con context completo (event_id, user_id, workflow_step, timestamp). Logs centralizados (Loki, ELK, Datadog) permiten queries cross-cutting.

Métricas operativas. Tiempo entre evento y envío de email (latencia), tasa de éxito por tipo de email, errores por proveedor de API, tasa de cancelaciones (eventos que cancelaron emails programados). Dashboards en Grafana o equivalente.

Replay capability. Capacidad de re-procesar eventos desde un punto en el tiempo. Útil cuando un bug en handler se descubre y se quiere re-ejecutar para usuarios afectados. Kafka soporta esto nativamente; otros brokers requieren tooling adicional.

Cuándo el modelo event-driven es overkill

El modelo event-driven es claramente superior para sistemas con muchos workflows complejos, pero tiene coste operativo significativo: stack más complejo (event bus + workflow engine + observabilidad), curva de aprendizaje del equipo, debugging más sofisticado.

Para sistemas pequeños con workflows simples (1-3 emails de onboarding, password reset estándar, billing notifications), cron-based workflow funciona perfectamente. La complejidad event-driven es overhead sin beneficio proporcional.

El umbral típico donde event-driven empieza a justificarse: 10+ workflows distintos, múltiples eventos triggers cross-cutting (mismo evento dispara cosas en email + analytics + ML pipeline), volumen alto donde latencia importa, o roadmap claro de añadir muchos más workflows.

Para SaaS en growth stage con producto que evoluciona rápido y emails que evolucionan con él, event-driven entrega flexibilidad arquitectónica que paga la inversión. Para sistemas estables con pocos workflows fijos, simple y suficiente sigue siendo elección racional.

¿Necesita ayuda con infraestructura email seria?

Authorize Hosting opera infraestructura email para equipos que se toman en serio sus envíos desde 2003. Relay SMTP, Email API, PowerMTA gestionado, servidores dedicados y deliverability gestionada con soporte directo en español.