DEV Community

Silviu Technology
Silviu Technology

Posted on

LLMs con aprobaciones por email sin ruido

Cuando un agente automatiza una tarea sensible, casi siempre aparece el mismo cuello de botella: alguien quiere una aprobacion humana antes de ejecutar el ultimo paso. El agente prepara el cambio, redacta el contexto y dispara un email. En demos esto parece facil. En producción, aveces termina en bandejas mezcladas, enlaces viejos y decisiones que nadie puede auditar bien.

Lo que me ha funcionado mejor no es "mandar mejor correos", sino tratar ese mensaje como un contrato operativo. Si el agente envia una solicitud de aprobacion, yo quiero poder demostrar tres cosas: a quién fue, qué accion estaba pidiendo y si ese enlace corresponde exactamnte a la ejecucion actual. Ahí un correo temporal desechable sirve como pieza de aislamiento, no como truco de growth.

El problema no es enviar el email

La mayor parte del stack ya sabe enviar correo. El problema reálmente aparece despues:

  • el agente reintenta y manda dos mensajes
  • un worker atrasado usa variables de una corrida anterior
  • el link de aprobacion apunta al host equivocado
  • dos reviewers comparten una bandeja y validan el correo incorrecto

Ese fallo es comun en flujos con LLMs porque el sistema parece coherente desde afuera. El modelo eligió una accion razonable, el backend devolvió 200, el job siguió vivo. Pero el paso humano quedó unido a un mensaje dificil de rastrear. Si alguna vez buscaste una alerta por "tem email" en logs o runbooks viejos, ya conoces ese tipo de caos pequeño pero caro.

Un contrato pequeño para agentes que piden aprobacion

Yo prefiero un contrato muy chico y verificable. No necesito snapshotear todo el HTML del correo. Necesito validar las partes que conectan decisión humana con ejecución automatizada:

  1. run_id unico por solicitud
  2. destinatario asociado a esa corrida
  3. asunto con intención explicita
  4. enlace con host, path y token esperados
  5. una marca de contexto que el reviewer pueda leer en 5 segundos

En otras palabras: diagrama en palabras. El agente genera un plan. El backend materializa una solicitud de aprobacion. El sistema de correo entrega un mensaje aislado. El verificador comprueba que esa salida sigue pegada al plan original. Nada mas, pero tampoco menos.

En entornos con previews o ramas efimeras, me gusta mucho el patrón de emails aislados por branch. La idea importante no es FastAPI como tal; es que cada ejecución tenga una frontera clara. Para operaciones mas delicadas, tambien me gusta cómo estos checks de email en ventanas de mantenimiento convierten el correo en una compuerta operativa y no en un detalle secundario.

La arquitectura que me funciono mejor

Mi versión minima se ve así:

agente -> crea solicitud -> persiste run_id -> envia email
reviewer -> abre link -> backend valida token + run_id
verificador -> consulta la bandeja aislada -> confirma 1 mensaje correcto
Enter fullscreen mode Exit fullscreen mode

Y el punto fino está en los constraints.

  • El agente nunca debe decidir el estado final solo por "email enviado".
  • El verificador debe leer una bandeja nueva por corrida, no una compartida.
  • El link de aprobacion debe resolverse contra el entorno activo, no contra una config vieja.
  • El estado aprobado debe ser idempotente, por si el usuario reabre el link.

Ese diseño es un poco menos comodo que mandar todo a ops@..., pero baja mucho el ruido. Tambien hace mas facil explicar un incidente. Si falló, sabes si falló la generacion, la entrega, el enlace o la confirmación humana. No estás adivinando.

Checkpoints antes de ponerlo en produccion

Estos son los checkpoints que yo revisaria antes de confiar el flujo:

  • Cada solicitud guarda run_id, actor, accion y TTL.
  • El asunto deja claro qué se aprueba y en qué entorno.
  • El cuerpo incluye una frase humana corta; no solo JSON disfrazado.
  • El token del link expira y se invalida al aprobar.
  • La prueba automatizada verifica cantidad de mensajes, host y path.

Si necesito un helper rápido, suelo arrancar con algo como esto:

RUN_ID="$(date -u +%Y%m%dT%H%M%SZ)"
MAILBOX="approve-$RUN_ID@example.test"

create_inbox "$MAILBOX"
trigger_agent_approval "$RUN_ID" "$MAILBOX"
assert_message_count "$MAILBOX" 1
assert_link_host "$MAILBOX" "preview.example.com"
assert_subject_contains "$MAILBOX" "Aprobacion requerida"
Enter fullscreen mode Exit fullscreen mode

No es sofisticado, y eso esta bien. En sistemas de Automation, la claridad gana mas veces que la brillantez. Un flujo de aprobacion por email tiene que sobrevivir guardias, retries y personas cansadas a las 2am. Si además de eso queda entendible para otro ingeniero una semana despues, ya hiciste la mayor parte del trabajo.

Q&A

¿Hace falta validar todo el contenido del correo?

No. Yo validaria solo lo que une la acción humana con la acción del sistema: destinatario, asunto, CTA y metadatos minimos. Validar el HTML entero suele meter ruido y falsos positivos.

¿Cuándo usar una bandeja temporal?

Cuando el objetivo es aislamiento por corrida, no retención larga. Para pruebas de aprobación y smoke checks es muy practico. Para auditoría regulatoria, necesitas otro circuito.

¿Dónde entra el LLM en todo esto?

En preparar el contexto y proponer la accion. La aprobacion no debería depender de "confío en el modelo", sino de un contrato observable. Ese cambio mental evita bastantes errores tontos, y honestamnte hace que operar el sistema sea mucho menos estresante.

Top comments (0)