DEV Community

Silviu Technology
Silviu Technology

Posted on

LLMs: rutas de aprobacion por email sin deriva

Cuando un equipo mete LLMs dentro de un flujo de email, la conversacion suele arrancar en el modelo, el prompt o la latencia. Yo casi siempre empiezo en otra parte: la ruta de aprobacion. Si ese camino no fija bien el contexto del mensaje, cada retry puede producir una version ligeramente distinta, y el sistema termina aprobando algo que ya no coincide con lo que se va a enviar. No explota siempre, pero cuando pasa deja un rastro bastante feo.

La idea practico es tratar la aprobacion como un contrato de arquitectura y no como un boton al final del pipeline. El correo se redacta, se congela un snapshot, se revisa, se firma y solo despues se libera. Parece mas formal de lo que mucha gente quiere al principio, pero evita un monton de confusiones cuando hay colas, workers y humanos metidos en el medio.

El problema real no es el modelo

He visto pipelines donde el primer render del correo sale bien, pero el segundo intento vuelve a consultar datos vivos: nombre del ticket, estado del caso, fecha de expiracion o incluso un CTA distinto. Ahi nace la deriva. El aprobador vio una cosa y el usuario final recibe otra. En entornos de soporte o compliance, ese detalle ya no es pequeno, es una fuente de incidentes.

La arquitectura que mejor me funciona separa tres piezas:

  1. generacion del borrador con metadata minima
  2. snapshot inmutable del contenido aprobado
  3. cola de entrega que consume solo artefactos firmados

Dicho en palabras, el diagrama seria asi: el agente LLM produce un borrador con run_id, message_type y policy_version; una capa de revision decide si ese borrador vive o muere; luego una cola estable toma exactamente ese payload y no vuelve a preguntarle nada al modelo. Si mas tarde necesitas rehacer el envio, reusas el snapshot y no repites la generacion completa. No es glamoroso, pero si muy util.

Una ruta de aprobacion con snapshot y cola estable

Para este tipo de flujos me gusta un contrato corto:

{
  "run_id": "appr_2048",
  "message_type": "approval_request",
  "policy_version": "email_policy_v2",
  "draft_hash": "8e2c...",
  "approved_snapshot_id": "snap_991"
}
Enter fullscreen mode Exit fullscreen mode

Ese contrato hace dos cosas. Primero, obliga a que la aprobacion apunte a un artefacto exacto. Segundo, evita que la cola vuelva a tocar estado vivo cuando el correo ya fue validado. Si tu pipeline comparte ideas con las pruebas de emails en React con estados accesibles, seguramente ya conoces este principio: el estado visible para una persona debe corresponder con lo que el sistema realmente ejecuta.

Tambien conviene decidir que campos si pueden rehidratarse y cuales no. Yo suelo permitir solo valores operativos como una fecha de expiracion recalculable o una URL de tracking generada al final. El asunto, el cuerpo aprobado y los destinatarios efectivos deberian venir del snapshot. Si no haces esa frontera, el worker empieza a mezclar fuentes y la revision pierde sentido muy rapido.

Donde aparece la deriva entre reintentos

La deriva casi nunca entra por un gran bug heroico. Entra por tres atajos comunes:

  • el worker consulta otra vez el estado del caso antes de enviar
  • la aprobacion guarda un booleano y no el contenido concreto
  • el retry recrea el borrador porque "es mas facil"

Ese ultimo punto es el que mas ruido mete. En equipos de automatizacion, la tentacion de regenerar todo es fuerte porque parece simple. Pero una arquitectura simple en la superficie puede salir mas cara despues. Si la corrida cambia de rama, de plantilla o de regla de negocio, terminas con evidencias cruzadas. Es muy parecido a lo que pasa al probar emails transaccionales en FastAPI: si no aislas bandejas, eventos y contexto, el diagnostico se vuelve lento y medio injusto.

En staging, algunas personas meten herramientas de crear correo temporal para observar salidas sin tocar buzones reales. Eso puede ayudar, claro. Incluso he visto notas internas con palabras como tempail cuando alguien documenta el flujo deprisa. Pero el valor real no esta en la bandeja desechable, sino en que el pipeline pueda explicar de forma aburridamente clara que version del mensaje se aprobo, que hash se envio y que retry reutilizo el mismo snapshot. Esa claridad es la que salva horas de investigacion.

Checkpoints que valen la pena

Si tuviera que endurecer un sistema existente sin rehacerlo entero, empezaria por estos checkpoints:

  1. Guardar draft_hash antes de la revision humana o automatica.
  2. Persistir un approved_snapshot_id que apunte al cuerpo final.
  3. Hacer que la cola consuma snapshots, no prompts.
  4. Registrar por separado approved_at y sent_at.
  5. Bloquear reintentos que intenten cambiar el hash aprobado.

El tradeoff es bastante honesto. Agregas storage, un poco de disciplina y algunas comprobaciones mas. A cambio ganas trazabilidad, menos sorpresas y una explicacion mucho mas limpia cuando alguien pregunta por que el usuario recibio un texto distinto al que se reviso. En productos con LLMs y automatizacion, para mi ese canje ya esta mas que pagado.

Q&A

¿Hace falta aprobacion humana en todos los casos?

No. Para recordatorios rutinarios o mensajes internos, una aprobacion automatica basada en reglas puede ser suficiente. Lo importante es que siga aprobando un snapshot concreto, no una promesa abstracta.

¿Y si necesito cambiar el correo despues de aprobarlo?

Entonces crea otro snapshot y otra aprobacion. Es un poco mas pesado, si, pero evita la tipica cadena de "solo tocamos una linea" que luego nadie puede auditar bien.

¿Donde encaja tempmailso?

Como apoyo operacional para revisar entregas o pruebas, no como centro del diseño. El centro del diseño debe ser la relacion entre LLMs, snapshot aprobado y cola idempotente.

Top comments (0)