ecosystem-wikicómo extender los templates sin romper sus convenciones
‹ todo el contenido

El viaje de un push, y en qué se diferencia

notification-stack
último cambio 2026-09-25

El push recorre el mismo camino que el email —validar, encolar, entregar, registrar— pero no es simétrico. Las diferencias no son cosméticas y conviene conocerlas antes de tocar cualquiera de los dos.

Diferencia 1 · Nadie aloja la plantilla

En email, Resend guarda el cuerpo y solo viaja un templateId. En push no hay proveedor que aloje nada, así que la plantilla guarda su contenido en la propia fila, en la columna content (un JSON con title, body y data), y es el worker quien renderiza.

Por eso la validación de plantilla es la contraria según el canal:

Canal Obligatorio Por qué
EMAIL templateId el cuerpo lo aloja Resend
PUSH content el cuerpo lo renderiza el worker

Ese assertChannelPayload corre al crear y al actualizar la plantilla, no al enviar — así una plantilla inválida ni siquiera llega a guardarse.

En PushService, título y cuerpo salen de la plantilla salvo que la llamada los pise (dto.title ?? templateContent.title). Y el data se mezcla por clave, ganando el que llega en la petición:

data: { ...templateContent.data, ...dto.data }

El comentario explica para qué: un código de verificación de un solo uso no puede vivir en la fila estática de la plantilla.

El render es deliberadamente tonto

Que el worker renderice abre una pregunta que el email no tiene: con qué motor. La respuesta es que no hay motor. core/render/interpolate.ts es un replace de doce líneas sobre {{variable}}:

template.replace(/\{\{\s*(\w+)\s*\}\}/g, (_m, key) => ...)

Sin condicionales, sin bucles, sin helpers. Su comentario dice por qué: templates stay data, not code. Las plantillas se guardan en base de datos y se editan desde el panel, así que un motor con lógica —Handlebars con helpers, por ejemplo— convertiría una fila de una tabla en algo ejecutable. Un push no necesita un if, y ese es todo el argumento para no poder tenerlo.

Dos consecuencias prácticas:

Diferencia 2 · Quién resuelve la credencial

Es la asimetría interna más fácil de tropezar:

O sea, MailerService no es el homólogo de FcmProvider aunque estén en la misma carpeta. El homólogo de FcmProvider es ResendService: los dos implementan ChannelProvider.

Y las credenciales se comportan distinto: la clave de Resend tiene respaldo global (RESEND_API_KEY), pero las de FCM no — sin cuenta de servicio del tenant, el push falla.

Diferencia 3 · El destinatario puede dejar de existir

Un email a una dirección muerta rebota y te enteras por el webhook. Un push a un token desinstalado lo rechaza FCM en el acto, y ese token no volverá a servir jamás.

Por eso el push tiene un camino de vuelta que el email no necesita: tokens de push muertos.

Lo que sí es igual