Skip to content

Recipient Allow/Deny Lists

Cada conexión de Telegram y SMTP puede declarar dos listas opcionales:

  • Allowlist: si tiene al menos un item, el servidor SOLO permite enviar a destinatarios presentes en la lista.
  • Denylist: si tiene al menos un item, el servidor rechaza cualquier envío a ese destinatario — aun si también está en la allowlist.

Las dos listas vacías significan "sin restricciones" (comportamiento default).

Shape de los items

ts
interface ILabeledValue {
  label: string;  // UX / agente: "Yo", "Soporte", etc.
  value: string;  // lo que se compara en enforcement
}

El label es metadata — la policy compara por value.

SlugCamposEnforcement aplica aComparación
telegramallowedChatIds, deniedChatIdschatId de sendMessage / replyToMessagestrict string equality (admite @usuario, IDs numéricos o cualquier formato)
smtpallowedRecipients, deniedRecipientssolo to (cc/bcc se ignoran, decisión de producto)case-insensitive, trimmed

Enforcement

Implementado como función pura en domain/policies/recipient-policy.ts:

ts
function validateRecipient(
  recipient: string,
  allowed: ILabeledValue[],
  denied: ILabeledValue[],
  compare: (policyValue, recipient) => boolean,
): Result<void>

Orden de evaluación (matching el contrato de UX):

  1. Si recipient ∈ deniedCONNECTION.RECIPIENT_DENIED (400).
  2. Si allowed no está vacía y recipient ∉ allowedCONNECTION.RECIPIENT_NOT_ALLOWED (400).
  3. Caso contrario → OK.

Ambos providers (TelegramProvider, SmtpProvider) invocan esta policy antes de tocar la red — así nunca se hace un roundtrip inútil a Telegram/nodemailer.

Awareness del agente AI

ConnectionsToolProvider.buildOne(...) enriquece la descripción de los tools send_telegram_message, reply_to_telegram_message y send_smtp_email con la lista allow/deny de esa conexión específica. Ejemplo de descripción inyectada:

Send a Telegram message to a chat using the specified Telegram bot connection.


Recipient Policy:
Allowed chat IDs (ONLY these recipients are permitted): "Yo" (25346), "Soporte" (153563).
Blocked chat IDs (must NEVER be used): "Ex-cliente" (99999).
Calling this tool with any other chat ID will be rejected at the server.

Cuando ambas listas están vacías se emite un mensaje explícito de "no restrictions" para evitar que el LLM asuma silencio = permiso limitado.

Edición post-creación (PATCH semantics)

Las listas viven en el JSON config de la conexión junto con el resto de campos (botToken, chatId, host, password, …). El endpoint PATCH /connections/:id acepta un config parcial: el backend hace MERGE sobre el config existente (decryptado) antes de validar y re-cifrar los campos sensibles.

Implicaciones:

  • El panel solo envía los campos que el usuario editó. Agregar un item a allowedChatIds NO requiere reenviar botToken.
  • Nunca mandes el literal ******** como password — el backend asumiría que es un valor nuevo y lo cifraría. buildChangedConfigPayload en el panel filtra campos sin cambios para evitarlo.
  • Si ambas listas quedan vacías tras una edición, ese [] explícito sí viaja en el payload (el usuario limpió intencionalmente la lista).

Errores

CódigoHTTPCuándo
CONNECTION.RECIPIENT_NOT_ALLOWED400La allowlist tiene items y el destinatario no está. metadata: { recipient }.
CONNECTION.RECIPIENT_DENIED400El destinatario está en la denylist. metadata: { recipient }.

Los mensajes llevan el valor del destinatario (label no; solo value) para que el agente pueda explicar al usuario por qué se rechazó.

Ejemplo de config Telegram con listas

json
{
  "botToken": "<encrypted>",
  "allowedChatIds": [
    { "label": "Yo", "value": "25346" },
    { "label": "Chat de soporte", "value": "153563" }
  ],
  "deniedChatIds": [
    { "label": "Ex-cliente", "value": "99999" }
  ]
}

Nota: Telegram ya no guarda un chatId "principal" en el config. El allowlist cubre ese caso (si solo querés un destino, lo pones como único item) y el caller debe pasar chatId explícitamente en cada sendMessage.

Tests clave

  • test/modules/connections/domain/policies/recipient-policy.spec.ts — matriz de casos (empty/allow/deny, comparadores).
  • test/modules/connections/infrastructure/providers/telegram.provider.spec.ts — enforcement en la capa provider, garantizando que NO se hace fetch al rechazar.
  • test/modules/connections/infrastructure/providers/smtp.provider.spec.ts — enforcement sobre to, ignorado en cc/bcc.
  • test/modules/connections/application/commands/update-connection.command.spec.ts — PATCH preserva el password existente cuando solo se edita host.