Sistema de notificaciones multicanal: plantillas, reintentos y preferencias
Diseña un sistema de notificaciones multicanal (email, SMS y Telegram) con plantillas, seguimiento de entrega, reintentos y preferencias de usuario consistentes.

Qué resuelve un sistema de notificaciones único
Cuando email, SMS y Telegram se construyen como características separadas, las grietas aparecen rápido. La "misma" alerta termina con redacción distinta, tiempos distintos y reglas distintas sobre quién la recibe. Los equipos de soporte entonces persiguen tres versiones de la verdad: una en el proveedor de correo, otra en la pasarela de SMS y otra en un registro del bot.
Un sistema de notificaciones multicanal arregla esto al tratar las notificaciones como un producto único, no como tres integraciones. Ocurre un evento (restablecimiento de contraseña, factura pagada, servidor caído) y el sistema decide cómo entregarlo en los canales según plantillas, preferencias de usuario y reglas de entrega. El mensaje puede formatearse de forma distinta por canal, pero mantiene coherencia en significado, datos y seguimiento.
La mayoría de los equipos acaban necesitando la misma base, sin importar por qué canal empezaron: plantillas versionadas con variables, seguimiento de estado de entrega ("en cola, enviado, entregado, fallido, por qué"), reintentos y fallbacks sensatos, preferencias de usuario con consentimiento y horas de silencio, y una pista de auditoría para que soporte vea qué pasó sin adivinar.
El éxito se ve aburrido, en el buen sentido. Los mensajes son predecibles: la persona correcta recibe el contenido correcto, en el momento adecuado, por los canales que permitió. Cuando algo falla, la resolución es sencilla porque cada intento está registrado con un estado claro y un código de motivo.
Una alerta de "nuevo inicio de sesión" es un buen ejemplo. La creas una vez, la llenas con los mismos datos de usuario, dispositivo y ubicación, y luego la entregas como email para detalles, SMS para urgencia y Telegram para confirmación rápida. Si el proveedor de SMS se queda sin respuesta, el sistema reintenta según lo programado, registra el timeout y puede recurrir a otro canal en lugar de perder la alerta.
Conceptos clave y un modelo de datos simple
Un sistema de notificaciones multicanal se mantiene manejable cuando separas "por qué notificamos" de "cómo lo entregamos". Eso significa un conjunto pequeño de objetos compartidos, más detalles específicos por canal solo donde realmente difieren.
Empieza con un evento. Un evento es un disparador con nombre como order_shipped o password_reset. Mantén nombres consistentes: minúsculas, guiones bajos y pasado cuando corresponda. Trata el evento como el contrato estable del que dependen plantillas y reglas de preferencia.
A partir de un evento, crea un registro de notificación. Esto es la intención dirigida al usuario: para quién es, qué pasó y qué datos se necesitan para renderizar el contenido (número de pedido, fecha de entrega, código de restablecimiento). Almacena aquí campos compartidos como user_id, event_name, locale, priority y scheduled_at.
Luego divide en mensajes por canal. Una notificación puede producir de 0 a 3 mensajes (email, SMS, Telegram). Los mensajes contienen campos específicos de canal como destino (dirección de correo, teléfono, chat_id de Telegram), template_id y contenido renderizado (asunto/cuerpo para email, texto corto para SMS).
Finalmente, registra cada envío como un intento de entrega. Los intentos incluyen provider request_id, marcas de tiempo, códigos de respuesta y un estado normalizado. Esto es lo que inspeccionas cuando un usuario dice: "No lo recibí."
Un modelo simple suele caber en cuatro tablas o colecciones:
- Event (catálogo de nombres de evento permitidos y valores por defecto)
- Notification (una por intención de usuario)
- Message (una por canal)
- DeliveryAttempt (un intento por envío)
Planifica la idempotencia desde el principio. Dale a cada notificación una clave determinista como (event_name, user_id, external_ref) para que los reintentos desde sistemas upstream no creen duplicados. Si un paso del flujo se vuelve a ejecutar, la clave de idempotencia evita que el usuario reciba dos SMS.
Almacena a largo plazo solo lo necesario para auditar (evento, notificación, estado final, marcas de tiempo). Mantén las colas de entrega a corto plazo y los payloads crudos de proveedores solo el tiempo necesario para operar y depurar.
Un flujo práctico de extremo a extremo (paso a paso)
Un sistema de notificaciones multicanal funciona mejor cuando trata "decidir qué enviar" separado de "enviarlo". Eso mantiene tu app rápida y hace que los fallos sean más fáciles de manejar.
Un flujo práctico se parece a esto:
-
Un productor de eventos crea una solicitud de notificación. Puede ser "restablecimiento de contraseña", "factura pagada" o "ticket actualizado". La solicitud incluye un ID de usuario, tipo de mensaje y datos de contexto (número de pedido, importe, nombre del agente de soporte). Almacena la solicitud de inmediato para tener una pista de auditoría.
-
Un enrutador carga reglas de usuario y de mensaje. Consulta las preferencias del usuario (canales permitidos, opt-ins, horas de silencio) y las reglas del mensaje (por ejemplo: alertas de seguridad deben intentar email primero). El enrutador decide un plan de canales, como Telegram, luego SMS, luego email.
-
El sistema encola trabajos de envío por canal. Cada trabajo contiene una clave de plantilla, canal y variables. Los trabajos van a una cola para que la acción del usuario no quede bloqueada por el envío.
-
Los workers de canal entregan vía proveedores. Email va por SMTP o una API de correo, SMS por una pasarela de SMS, Telegram a través de tu bot. Los workers deben ser idempotentes, de modo que reintentar el mismo trabajo no genere duplicados.
-
Las actualizaciones de estado fluyen a un único lugar. Los workers registran en cola, enviado, fallado y, cuando esté disponible, entregado. Si un proveedor solo confirma "accepted", regístralo también y trátalo distinto de entregado.
-
Los fallbacks y reintentos se ejecutan desde el mismo estado. Si Telegram falla, el enrutador (o un worker de reintentos) puede programar SMS a continuación sin perder el contexto.
Ejemplo: un usuario cambia su contraseña. Tu backend emite una sola solicitud con el usuario y la IP. El enrutador ve que el usuario prefiere Telegram, pero las horas de silencio lo bloquean por la noche, así que programa email ahora y Telegram por la mañana, mientras registra ambos bajo la misma notificación.
Si vas a implementar esto en AppMaster, guarda la solicitud, los trabajos y las tablas de estado en el Data Designer y expresa la lógica de enrutamiento y reintentos en el Business Process Editor, con el envío manejado de forma asíncrona para que la UI siga siendo responsiva.
Estructura de plantillas que funciona entre canales
Un buen sistema de plantillas parte de una idea: notificas sobre un evento, no "envías un email" o "envías un SMS". Crea una plantilla por evento (Restablecimiento de contraseña, Pedido enviado, Pago fallido) y almacena variantes por canal bajo ese mismo evento.
Mantén las mismas variables en cada variante de canal. Si el email usa first_name y order_id, SMS y Telegram deben usar exactamente los mismos nombres. Esto evita errores sutiles donde un canal renderiza bien y otro muestra campos vacíos.
Una forma de plantilla simple y repetible
Para cada evento, define un pequeño conjunto de campos por canal:
- Email: asunto, preheader (opcional), cuerpo HTML, texto alternativo
- SMS: cuerpo en texto plano
- Telegram: cuerpo en texto plano, más botones opcionales o metadata corta
Lo único que cambia por canal es el formato, no el significado.
SMS necesita reglas especiales porque es corto. Decide desde el principio qué ocurre cuando el contenido es demasiado largo y hazlo consistente: fija un límite de caracteres, elige una regla de truncado (cortar y añadir ... o eliminar líneas opcionales primero), evita URLs largas y puntuación extra, y coloca la acción clave al principio (código, fecha límite, siguiente paso).
Localización sin copiar lógica de negocio
Trata el idioma como un parámetro, no como un flujo separado. Almacena traducciones por evento y canal, y luego renderiza con las mismas variables. La lógica de "Pedido enviado" sigue siendo la misma mientras asunto y cuerpo cambian por locale.
Un modo de vista previa se paga solo. Renderiza plantillas con datos de ejemplo (incluyendo casos límite como un nombre largo) para que soporte pueda verificar las variantes de email, SMS y Telegram antes de publicarlas.
Estados de entrega en los que puedas confiar y depurar
Una notificación solo es útil si luego puedes responder a una pregunta: ¿qué le pasó? Un buen sistema de notificaciones multicanal separa el mensaje que querías enviar de cada intento de entregarlo.
Empieza con un pequeño conjunto de estados compartidos que signifiquen lo mismo en email, SMS y Telegram:
- en cola: aceptado por tu sistema, esperando un worker
- en envío: un intento de entrega está en curso
- enviado: entregado al API del proveedor con éxito
- fallido: el intento terminó con un error sobre el que puedes actuar
- entregado: tienes evidencia de que llegó al usuario (cuando sea posible)
Mantén estos estados en el registro principal del mensaje, pero registra cada intento en una tabla de historial. Ese historial es lo que facilita la depuración: intento #1 falló (timeout), intento #2 tuvo éxito, o SMS funcionó mientras el email seguía rebotando.
Qué almacenar por intento
Normaliza las respuestas de los proveedores para poder buscar y agrupar problemas incluso cuando usan palabras distintas.
- provider_name y provider_message_id
- response_code (un código normalizado como TIMEOUT, INVALID_NUMBER, BOUNCED)
- raw_provider_code y raw_error_text (para casos de soporte)
- started_at, finished_at, duration_ms
- channel (email, sms, telegram) y destination (enmascarado)
Planifica el éxito parcial. Una notificación puede crear tres mensajes de canal que compartan el mismo parent_id y contexto de negocio (order_id, ticket_id, alert_type). Si el SMS se envía pero el email falla, aún quieres la historia completa en un solo lugar, no tres incidentes sin relación.
Qué significa realmente "entregado"
"Enviado" no es "entregado". Para Telegram, quizá solo sepas que la API aceptó el mensaje. Para SMS y email, la entrega depende a menudo de webhooks o callbacks del proveedor, y no todos los proveedores son igual de fiables.
Define entregado por canal desde el inicio. Usa entrega confirmada por webhook cuando esté disponible; si no, trata entregado como desconocido y sigue reportando enviado. Eso mantiene los informes honestos y las respuestas de soporte consistentes.
Reintentos, fallbacks y cuándo dejar de intentar
Los reintentos son donde los sistemas de notificaciones suelen fallar. Reintentar demasiado rápido crea tormentas. Reintentar para siempre genera duplicados y dolores de cabeza para soporte. El objetivo es simple: volver a intentar cuando haya una posibilidad real de éxito y parar cuando no la haya.
Empieza clasificando fallos. Un timeout del proveedor de correo, un 502 de la pasarela de SMS o un error temporal de la API de Telegram suelen ser reintentables. Una dirección de correo mal formada, un número de teléfono que falla la validación o un chat de Telegram que bloqueó tu bot no lo son. Tratar estos igual desperdicia dinero y satura logs.
Un plan de reintentos práctico está acotado y usa backoff:
- Intento 1: enviar ahora
- Intento 2: después de 30 segundos
- Intento 3: después de 2 minutos
- Intento 4: después de 10 minutos
- Parar después de una edad máxima (por ejemplo, 30-60 minutos para alertas)
Detenerse necesita un lugar real en tu modelo de datos. Marca el mensaje como dead-letter (o fallado permanentemente) una vez que exceda los límites de reintento. Conserva el último código de error y un mensaje corto para que soporte pueda actuar sin adivinar.
Evita envíos repetidos tras un éxito con idempotencia. Crea una clave de idempotencia por mensaje lógico (a menudo notification_id + user_id + channel). Si un proveedor responde tarde y vuelves a intentar, el segundo intento debe reconocerse como duplicado y saltarse.
Los fallbacks deben ser deliberados, no pánicos automáticos. Define reglas de escalamiento según severidad y tiempo. Ejemplo: un restablecimiento de contraseña no debe derivarse a otro canal (riesgo de privacidad), pero una alerta de incidente de producción podría intentar SMS tras dos intentos fallidos en Telegram y luego email tras 10 minutos.
Preferencias de usuario, consentimiento y horas de silencio
Un sistema de notificaciones se siente "inteligente" cuando respeta a las personas. La forma más simple de lograrlo es permitir que los usuarios elijan canales por tipo de notificación. Muchos equipos agrupan tipos en categorías como seguridad, cuenta, producto y marketing porque las reglas y requisitos legales difieren.
Empieza con un modelo de preferencias que funcione incluso cuando un canal no esté disponible. Un usuario puede tener correo pero no teléfono, o no haber conectado Telegram aún. Tu sistema multicanal debe tratar eso como normal, no como error.
La mayoría de sistemas acaban necesitando un conjunto compacto de campos: tipo de notificación (seguridad, marketing, facturación), canales permitidos por tipo (email, SMS, Telegram), consentimiento por canal (fecha/hora, origen y prueba si hace falta), razón de exclusión por canal (elección del usuario, email rebotado, respuesta "STOP") y una regla de horas de silencio (inicio/fin más zona horaria del usuario).
Las horas de silencio son donde los sistemas suelen fallar. Guarda la zona horaria del usuario (no solo un offset) para que los cambios por horario de verano no sorprendan a nadie. Cuando un mensaje está programado durante horas de silencio, no lo marqués como fallado. Márcalo como diferido y elige el siguiente momento permitido para enviar.
Los valores por defecto importan, especialmente para alertas críticas. Un enfoque común: las notificaciones de seguridad ignoran horas de silencio (pero siguen respetando exclusiones duras donde sea requerido), mientras que las actualizaciones no críticas siguen las horas de silencio y las elecciones de canal.
Ejemplo: un restablecimiento de contraseña debe enviarse de inmediato por el canal permitido más rápido. Un resumen semanal debe esperar hasta la mañana y omitir SMS a menos que el usuario lo haya habilitado explícitamente.
Operaciones: monitorización, logs y flujos de trabajo de soporte
Cuando las notificaciones tocan email, SMS y Telegram, los equipos de soporte necesitan respuestas rápidas: ¿lo enviamos, llegó y qué falló? Un sistema de notificaciones multicanal debe sentirse como un único lugar para investigar, aunque use varios proveedores detrás.
Empieza con una vista administrativa simple que cualquiera pueda usar. Hazla buscable por usuario, tipo de evento, estado y ventana temporal, y muestra los intentos más recientes primero. Cada fila debe revelar el canal, la respuesta del proveedor y la siguiente acción planificada (reintento, fallback o detenido).
Métricas que detectan problemas temprano
Los outages raramente aparecen como un único error claro. Rastrea un pequeño conjunto de números y revísalos con regularidad:
- Tasa de envío por canal (mensajes por minuto)
- Tasa de fallos por proveedor y código de fallo
- Tasa de reintentos (cuántos mensajes necesitaron un segundo intento)
- Tiempo hasta entrega (en cola a entregado, p50 y p95)
- Tasa de descarto (detenido por preferencias de usuario, consentimiento o reintentos máximos)
Correlaciona todo. Genera un ID de correlación cuando ocurre el evento (como "invoice overdue") y pásalo por plantillas, colas, llamadas a proveedores y actualizaciones de estado. En los logs, ese ID es el hilo a seguir cuando un evento se expande a varios canales.
Reproducciones amigables para soporte sin sorpresas
Las reejecuciones son esenciales, pero necesitan guardarraíles para no spammear a la gente ni cobrar doble. Un flujo de reenvío seguro suele significar: reenviar solo un ID de mensaje específico (no todo el lote de eventos), mostrar la versión exacta de la plantilla y el contenido renderizado antes de enviar, requerir una razón y almacenar quién disparó el reenvío, bloquear el reenvío si el mensaje ya fue entregado a menos que se fuerce explícitamente, y aplicar límites de ritmo por usuario y por canal.
Seguridad y privacidad básicas para notificaciones
Un sistema de notificaciones multicanal maneja datos personales (correos, teléfonos, chat IDs) y suele cubrir momentos sensibles (inicios de sesión, pagos, soporte). Asume que cada cuerpo de mensaje y cada línea de log podría verse después, y diseña para limitar qué se almacena y quién puede verlo.
Mantén datos sensibles fuera de las plantillas siempre que sea posible. Una plantilla debe ser reutilizable y anodina: "Tu código es {{code}}" está bien, pero evita incluir detalles completos de cuenta, tokens largos o cualquier cosa que pueda usarse para tomar control de una cuenta. Si un mensaje debe incluir un código de un solo uso o un token de restablecimiento, almacena solo lo necesario para verificarlo (por ejemplo, un hash y una expiración), no el valor en bruto.
Cuando almacenes o registres eventos de notificación, enmascara agresivamente. Un agente de soporte suele necesitar saber que se envió un código, no el código en sí. Lo mismo aplica a números de teléfono y correos: guarda el valor completo para la entrega, pero muestra una versión enmascarada en la mayoría de pantallas.
Controles mínimos que previenen la mayoría de incidentes
- Acceso basado en roles: solo un conjunto pequeño de roles puede ver cuerpos de mensaje e información completa del destinatario.
- Separa el acceso de depuración del acceso de soporte para que la solución de problemas no se convierta en una fuga de privacidad.
- Protege los endpoints de webhook: usa callbacks firmados o secretos compartidos, valida timestamps y rechaza orígenes desconocidos.
- Encripta campos sensibles en reposo y usa TLS en tránsito.
- Define reglas de retención: conserva logs detallados brevemente y luego guarda solo agregados o identificadores hashed.
Un ejemplo práctico: si un SMS de restablecimiento falla y se recurre a Telegram, almacena el intento, el estado del proveedor y el destinatario enmascarado, pero evita guardar el enlace de restablecimiento en la base de datos o en logs.
Escenario de ejemplo: una alerta, tres canales, resultados reales
Una cliente, Maya, tiene habilitados dos tipos de notificación: Restablecimiento de contraseña y Nuevo inicio de sesión. Ella prefiere Telegram primero y luego email. Solo quiere SMS como fallback para restablecimientos de contraseña.
Una noche, Maya solicita un restablecimiento de contraseña. El sistema crea un único registro de notificación con un ID estable y luego lo expande en intentos por canal según sus preferencias actuales.
Lo que Maya ve es simple: un mensaje de Telegram llega en segundos con un código corto y tiempo de expiración. No llega nada más porque Telegram tuvo éxito y no fue necesario fallback.
Lo que el sistema registra es más detallado:
- Notification: type=PASSWORD_RESET, user_id=Maya, template_version=v4
- Intento #1: channel=TELEGRAM, status=ENVIADO luego ENTREGADO
- No se crearon intentos de email ni SMS (política: detener tras el primer éxito)
Más tarde esa semana, se dispara una alerta de Nuevo inicio de sesión desde un dispositivo nuevo. Las preferencias de Maya para alertas de inicio son Telegram únicamente. El sistema envía Telegram, pero el proveedor devuelve un error temporal. El sistema reintenta dos veces con backoff, luego marca el intento como FALLIDO y se detiene (no se permite fallback para este tipo de alerta).
Ahora un fallo real: Maya pide otro restablecimiento de contraseña mientras viaja. Telegram se envía, pero el fallback por SMS está configurado si Telegram no entrega en 60 segundos. El proveedor de SMS se queda sin respuesta. El sistema registra el timeout, reintenta una vez y el segundo intento tiene éxito. Maya recibe el SMS un minuto después.
Cuando Maya contacta a soporte, buscan por usuario y ventana temporal y ven de inmediato el historial de intentos: marcas de tiempo, códigos de respuesta del proveedor, contador de reintentos y resultado final.
Lista rápida, errores comunes y siguientes pasos
Un sistema de notificaciones multicanal es más fácil de operar cuando puedes responder dos preguntas rápido: "¿Qué intentamos enviar exactamente?" y "¿Qué pasó después?" Usa esta lista antes de añadir más canales o eventos.
Lista rápida
- Nombres de evento claros y propiedad (por ejemplo,
invoice.overduepropiedad de billing) - Variables de plantilla definidas una vez (requeridas vs opcionales, valores por defecto, reglas de formato)
- Estados acordados desde el inicio (created, queued, sent, delivered, failed, suppressed) y qué significa cada uno
- Límites y backoff de reintentos (máx. intentos, espaciamiento, regla de parada)
- Reglas de retención (cuánto tiempo guardas cuerpos de mensaje, respuestas de proveedores e historial de estado)
Si haces solo una cosa, escribe en palabras claras la diferencia entre enviado y entregado. Enviado es lo que hizo tu sistema. Entregado es lo que reporta el proveedor (y puede estar retrasado o faltar). Confundirlos confundirá a soporte y stakeholders.
Errores comunes a evitar
- Tratar "enviado" como éxito y reportar tasas de entrega infladas
- Dejar que las plantillas por canal se desalineen hasta que email, SMS y Telegram se contradigan
- Reintentar sin idempotencia, causando duplicados cuando proveedores timeoutean pero luego aceptan el mensaje
- Reintentar para siempre, convirtiendo una caída temporal en un incidente ruidoso
- Almacenar demasiados datos personales en logs y registros de estado "por si acaso"
Empieza con un evento y un canal principal, luego añade un segundo canal como fallback (no como envío paralelo). Una vez que el flujo es estable, expande evento por evento, manteniendo plantillas y variables compartidas para que los mensajes sigan coherentes.
Si quieres construir esto sin codificar cada pieza, AppMaster (appmaster.io) encaja de forma práctica para las partes centrales: modela eventos, plantillas e intentos de entrega en el Data Designer, implementa enrutamiento y reintentos en el Business Process Editor y conecta email, SMS y Telegram como integraciones mientras mantienes el seguimiento en un solo lugar.


