Koru Contact Form

Formularios de contacto dinámicos con notificación por email y autorespuesta. Diseñás campos, estilo y avisos desde el panel de Koru — en el sitio se instala una sola vez.

Koru Contact Form permite crear formularios de contacto (campos, validación de obligatoriedad, ancho de columna, textos y colores) y recibir los envíos en un panel administrativo con listado completo, más notificación por email al administrador y autorespuesta opcional al usuario que completó el formulario. Incluye protección anti-spam por honeypot y guarda cada envío con un snapshot de la configuración vigente al momento del envío.

Todo el contenido y comportamiento —campos, diseño, emails— se configura desde el panel de Koru, sin volver a tocar el código del sitio. En el sitio se instala una sola vez.

Vas a necesitar tu Website ID required, tu App ID required, el Form ID required y la App Manager URL required. Los cuatro vienen resueltos en el código que emite el dashboard de Koru Contact Form → tu formulario → botón Copiar Código (modal Código de Instalación). No hace falta buscarlos uno por uno.

Qué no hace Koru Contact Form

No notifica por WhatsApp ni SMS: el único canal de salida es el email. No hereda los estilos de tu tienda (solo el color de acento es configurable). No admite más de un formulario por página. No expone un archivo de override CSS. Su única protección anti-spam es el honeypot; no incluye captcha.

Antes de empezar

Responsables recomendados

TareaResponsable habitual
Crear el formulario y definir los camposMarketing, ecommerce o dueño del sitio
Pegar el snippet en el sitioDesarrollador o agencia
Cargar los destinatarios y el texto de los emailsQuien va a atender las consultas
Revisar los envíos en el panelAtención al cliente o ventas

En un equipo chico una misma persona puede cubrir todos los roles.

Credenciales requeridas

CredencialAtributo en el snippetEstadoDe dónde se saca
Website IDdata-website-idREQUIREDModal Código de Instalación del dashboard de la app. Es el ID del sitio al que está asignado el formulario.
App IDdata-app-idREQUIREDMismo modal. Es el ID de la app Koru Contact Form, igual para todos los formularios de la cuenta.
Form ID (slug)data-custom-dataREQUIREDSe autogenera al crear el formulario (formato koru-xxxxxxxxx) y se muestra en el header del builder como ID: koru-…. También lo emite el modal.
App Manager URLdata-app-manager-urlREQUIREDMismo modal. Es el host contra el que el widget valida su autorización (<APP-MANAGER-URL>/api/auth/widget). Copialo tal cual lo emite el panel.
API URLdata-api-urlOpcional (no se lee)El panel lo emite, pero ni el SDK ni el widget lo leen: la URL del backend está fija en el build del widget. No afecta el funcionamiento.

Los cuatro obligatorios salen del mismo lugar y ya vienen resueltos en el snippet, así que en la práctica no se cargan a mano: se copia el bloque completo.

El modal Código de Instalación solo se abre si Koru Suite valida que tu usuario tiene permisos sobre ese formulario y ese sitio. Si ves "Acceso denegado", el problema es de permisos en Koru, no del código.

Orden de implementación

Este es el recorrido completo, de punta a punta. Seguilo en orden para no dejar el formulario publicado a medias — sobre todo para que no haya envíos que no le lleguen a nadie.

Activá la app para el sitio en Koru

En el panel de Koru, la app tiene que estar activa para ese website. Si no lo está, el widget no se monta: el SDK loguea Widget not authorized y la página no muestra nada.

Creá el formulario y definí los campos

El Form ID se genera en este paso, así que no se puede instalar antes de crear el formulario. Armá los campos ahora y, si querés autorespuesta, incluí un campo de tipo email desde el principio.

Cargá admin_email

Sin destinatario, los envíos se guardan pero no se manda ningún email. Es el paso que más se olvida.

Ajustá diseño y textos

Definí display_type, accent_color, el texto del botón y el mensaje de éxito. Si vas a redirigir después del envío, cargá redirect_url.

Instalá el snippet en el sitio

Con el formulario ya armado, copiá el código y pegalo en tu sitio. El detalle está en Instalación.

Hacé un envío de prueba real

Completá el formulario desde el sitio publicado y verificá las tres cosas: que el envío aparezca en el panel, que llegue el email al administrador y —si la activaste— que llegue la autorespuesta al email que cargaste.

Revisá las variables del email

Si usás {{Nombre}} u otra variable de campo, confirmá en el email recibido que se resolvió con el valor cargado y no quedó el texto crudo. Las variables usan el label exacto del panel.

Instalación

Koru Contact Form se instala con un único método: el código de integración <script>. Sirve para cualquier plataforma que permita insertar HTML en la página (VTEX, Shopify, WordPress, Tiendanube, HTML propio).

Antes de instalar

El formulario ya tiene que estar creado —el snippet incluye su Form ID— y la app tiene que estar activa para ese sitio en Koru. Ver Orden de implementación.

La instalación tiene dos partes obligatorias

El <script> del widget y un <div class="koru-contact-form"> que marca dónde se monta. Ese <div> es requerido incluso en modo botón flotante: sin él el widget no se renderiza y registra un error en consola.

Copiá el código desde el dashboard de la app

El snippet lo emite la propia app: panel de Koru → Configurar AppsKoru Contact Form → tu formulario → botón Copiar Código (modal Código de Instalación). Viene completo, con el Website ID, el App ID, el Form ID y la App Manager URL ya resueltos.

Dónde vive el dashboard

El dashboard de Koru Contact Form todavía no tiene dominio propio: hoy se accede en https://koru-dashboard.pages.dev/. Cuando se le asigne el dominio definitivo, la URL cambia; el resto del procedimiento queda igual.

No lo busques en el Código de integración de la ficha del sitio: ese generador es genérico para widgets embebibles y no sirve para esta app — no emite el Form ID ni el contenedor.

Pegalo en el <body> de tu sitio

Va en el lugar exacto donde querés que aparezca el formulario. En modo Inline el formulario se renderiza dentro del <div>; en modo Floating el <div> solo actúa como punto de montaje y el panel se posiciona con position: fixed.

Así se ve el código que emite el panel:

<!-- Contenedor del Widget -->
<div class="koru-contact-form" data-form-id="TU-FORM-ID"></div>
 
<!-- Koru SDK & Widget Script -->
<script
  src="URL-DEL-WIDGET-QUE-EMITE-EL-PANEL"
  data-website-id="TU-WEBSITE-ID"
  data-app-id="TU-APP-ID"
  data-custom-data="TU-FORM-ID"
  data-api-url="URL-DE-API-QUE-EMITE-EL-PANEL"
  data-app-manager-url="URL-APP-MANAGER-QUE-EMITE-EL-PANEL"
  async
></script>

Pegalo tal cual, sin sacarle atributos: data-api-url y el data-form-id del contenedor hoy no se leen, pero forman parte del código que emite el panel.

Verificá que cargue

Recargá la página. En modo Inline vas a ver un spinner dentro del contenedor mientras carga y después el formulario; en modo Floating, la burbuja aparece cuando el widget terminó de montarse. Si no ves nada, revisá la consola del navegador y mirá la sección No aparece nada en la página.

Usá el valor de src tal cual lo emite el panel. Hoy sale sin nombre de archivo (termina en /); si el widget no carga y en la consola ves un error de red sobre esa URL, probá agregándole /index.js al final.

Atributos del script

srcstring · urlrequired

URL del bundle del widget. El bundle es autocontenido (incluye React y ReactDOM).

data-website-idstring · uuidrequired

ID del sitio en Koru. Debe coincidir con el sitio asignado al formulario en el panel; si no coincide, el backend responde 403 Unauthorized website ID.

data-app-idstring · uuidrequired

ID de la app en Koru. Si falta, el SDK lanza Missing required data attributes y no renderiza nada.

data-custom-datastringrequired

Form ID (slug) del formulario a mostrar. Es lo que determina qué formulario carga. Si se omite, el widget cae al app_id como identificador y la búsqueda de configuración falla.

data-app-manager-urlstring · urlrequired

Host de autorización del SDK. Si falta, el SDK lanza Missing required data attributes.

data-api-urlstring · urloptional

Emitido por el panel, ignorado por el código.

asyncbooloptional

Recomendado — el panel lo emite. El script no bloquea el render: el SDK espera DOMContentLoaded, resuelve la autorización y recién entonces monta el formulario.

Atributos del contenedor

classstringrequired

Tiene que ser exactamente koru-contact-form: es el selector que busca el widget (document.querySelector('.koru-contact-form')).

data-form-idstringoptional

El snippet del panel lo incluye, pero el widget no lo lee: el formulario se resuelve por el data-custom-data del script.

Reglas de posición e instancias

  • Un solo formulario por página. El widget usa querySelector, es decir el primer .koru-contact-form del DOM. Dos scripts en la misma página apuntan al mismo contenedor y el segundo sobrescribe el render del primero. Para varios formularios, usá páginas distintas.
  • El posicionamiento del modo flotante se resuelve por configuración (position en el panel), no por CSS del sitio.
  • El contenedor puede ir en cualquier parte del <body>. En modo Floating su posición en el DOM es indistinta, porque el panel se muestra con position: fixed.

Configuración (cómo usar)

Una vez instalada, todo se maneja desde el panel de KoruConfigurar AppsKoru Contact Form. La app abre su propio dashboard —hoy en https://koru-dashboard.pages.dev/, todavía sin dominio propio— con tres pestañas por formulario: Campos, Diseño y Emails. Ningún cambio requiere volver a tocar el sitio.

Identificación del formulario

CampoDescripción
formIdSlug único autogenerado al crear el formulario (koru-xxxxxxxxx). Solo lectura: no se puede cambiar después de crear el formulario, y es el valor que va en data-custom-data.
titleNombre del formulario. Se muestra como encabezado del formulario en el sitio y se usa como variable {{FormName}} en los emails. Se guarda también en name.
website_idSitio web destino, elegido de la lista de sitios de tu cuenta Koru (Sitio Web Destino en la pestaña Campos). Debe coincidir con el data-website-id del snippet.

Campos (fields_config)

Cada campo del array tiene estas propiedades:

CampoDescripción
idIdentificador interno autogenerado (field_<timestamp>). Es la clave con la que se guarda el valor en el envío.
labelEtiqueta visible. También es el nombre de la columna en el email al administrador y el nombre de la variable en plantillas (ej.: label: "Nombre"{{Nombre}}).
typeTipo de campo (ver tabla siguiente).
requiredSi es obligatorio. Se aplica como validación nativa del navegador (required).
width100% (campo a ancho completo) o 50% (dos campos por fila).
optionsSolo para select: opciones separadas por coma, ej.: Consulta, Soporte, Ventas.

Tipos disponibles en el panel:

typeQué renderizaCampos que usa
textInput de texto simplelabel, required, width
emailInput de email con validación de formato del navegadorlabel, required, width
textareaÁrea de texto multilínea (alto mínimo 100px, redimensionable)label, required, width
numberInput numéricolabel, required, width
selectDesplegable con opción inicial "Seleccionar…"label, required, width, options

Incluí al menos un campo type: email si querés autorespuesta

El backend detecta el email del usuario tomando el primer campo de tipo email. Sin ese campo, la autorespuesta no se envía (la notificación al administrador sí) y tampoco se setea el reply_to del email al admin.

Otras notas de comportamiento:

  • Los campos no renderizan placeholder ni reglas de validación adicionales (min/max/regex), aunque el modelo de datos de la API las mencione.
  • El orden de los campos en el panel es el orden de render.

Diseño (layout_settings)

OpciónQué haceCampos que usa
display_type: "Inline"El formulario se renderiza embebido dentro del <div class="koru-contact-form">, ancho 100% con máximo 500px y centrado. Es el valor por defecto.accent_color, submit_text, success_msg, redirect_url
display_type: "Floating"Muestra una burbuja circular de 60px fija en pantalla; al hacer clic abre el formulario como modal centrado con fondo oscurecido y blur.position, bubble_icon, accent_color, submit_text, success_msg, redirect_url

Cualquier valor distinto de Inline se comporta como flotante.

CampoDescripción
positionUbicación de la burbuja. El panel ofrece Bottom-Right y Bottom-Left; el widget también interpreta Top-Right y Top-Left si el valor llega por API. Ignorado en modo Inline (el panel lo deshabilita).
bubble_iconÍcono de la burbuja. Envelope → sobre, User → usuario, Chat y Question → globo de chat (ambos caen en el ícono por defecto). Ignorado en modo Inline.
accent_colorColor del botón de envío, de la burbuja, del foco de los inputs y del ícono de éxito. Se elige con color picker o hex.
submit_textTexto del botón de envío (ej.: Enviar consulta). Se muestra junto a un ícono de avión.
success_msgMensaje bajo el título "¡Enviado!" después de un envío exitoso.
redirect_urlOpcional. Si tiene valor, redirige a esa URL 2 segundos después del envío exitoso (ej.: https://ejemplo.com/gracias). Si queda vacío, el usuario se queda en la pantalla de éxito.

Si accent_color queda vacío, el widget usa #4F46E5 (violeta): no hereda los estilos de la tienda. El resto de la paleta (fondos, bordes, tipografías) es fija y no configurable.

Emails (email_settings)

CampoDescripción
admin_emailObligatorio para que se envíen emails. Uno o varios destinatarios separados por coma (ventas@empresa.com, soporte@empresa.com). Si está vacío, el envío se guarda igual en el panel pero no se manda ningún email y queda registrado el error en el log del envío.
subject_lineAsunto del email al administrador. Admite variables (ver abajo). Si queda vacío, se usa Nuevo mensaje de <título del formulario>.
autoresponder_enabledActiva la autorespuesta al usuario que completó el formulario. Requiere un campo de tipo email en el formulario. (autoresponder funciona como alias legacy del mismo toggle.)
autoresponder_subjectAsunto de la autorespuesta. Si queda vacío: Gracias por tu contacto.
autoresponder_messageCuerpo de la autorespuesta, en texto plano. Los saltos de línea se respetan y se convierten en párrafos. Si queda vacío: Gracias por contactarnos.

Los emails salen desde Koru Contact Form <contactform@korusuite.com>. Si el formulario tiene un campo de tipo email, el aviso al administrador lleva reply_to con la dirección que dejó el usuario, así que se puede responder directo desde el cliente de correo; sin ese campo, el reply_to no se setea.

Variables disponibles en asuntos y mensajes

VariableValor
{{<label del campo>}}El valor que cargó el usuario en ese campo, ej.: {{Nombre}}, {{Teléfono}}. Usa el label exacto configurado en el panel.
{{Name}}Primer valor disponible entre el campo con label Nombre Completo, el campo con label Nombre, o la clave name. Si no encuentra ninguno: Cliente.
{{Email}}Email detectado del usuario (vacío si no hay campo email).
{{FormName}}Título del formulario.
{{PageURL}}URL de la página desde donde se envió (N/A si no llega).
{{Timestamp}}Fecha y hora del envío.

Ejemplo: subject_line: "Nueva consulta de {{Nombre}} — {{FormName}}".

Estado del formulario

CampoDescripción
isActive1 = activo. Con isActive = 0 el widget sigue renderizando el formulario, pero el envío falla con 404 Form not found or inactive.
Eliminar formularioEs un borrado lógico: el formulario deja de cargar en el sitio (404 Form deleted) y los envíos históricos se conservan para auditoría.

Datos y privacidad

  • Cada envío se guarda en Koru junto con un snapshot de la configuración vigente al momento del envío, así un cambio posterior de campos no altera el histórico.
  • Se registra la URL de la página desde donde se envió (disponible como {{PageURL}}) y la fecha y hora ({{Timestamp}}).
  • Eliminar el formulario es un borrado lógico: los envíos históricos se conservan.
  • Los envíos marcados como spam por el honeypot quedan archivados en el panel y no disparan emails.
  • El resultado del envío de cada email queda registrado en el log del envío.

Límites funcionales

La versión actual:

  • Muestra un solo formulario por página (el widget toma el primer .koru-contact-form del DOM).
  • Notifica solo por email; no hay otros canales de salida.
  • Ofrece cinco tipos de campo: text, email, textarea, number y select.
  • No renderiza placeholder ni validaciones adicionales (min/max/regex).
  • Acepta las opciones de un select únicamente como string separado por comas.
  • Detecta el email del usuario solo desde el primer campo de tipo email.
  • Permite configurar únicamente el color de acento; el resto de la paleta y la tipografía son fijas.
  • No expone handles ni variables CSS, y no tiene archivo de override.
  • No incluye captcha: la protección anti-spam es el honeypot.
  • No permite cambiar el formId después de crear el formulario.
  • Tiene los textos de la interfaz fijos en español (Seleccionar…, ¡Enviado!, los mensajes de error): no son traducibles. Lo que cargás vos —labels, botón, mensaje de éxito, asuntos y cuerpo de la autorespuesta— sí puede estar en cualquier idioma.

Checklist antes de publicar

  • El formulario tiene todos los campos que necesitás, con required y width definidos.
  • Hay un campo de tipo email si vas a usar autorespuesta.
  • admin_email cargado con los destinatarios correctos.
  • subject_line revisado, con las variables escritas igual que el label del campo.
  • Autorespuesta configurada (asunto y mensaje) si la vas a usar.
  • display_type y accent_color definidos; position y bubble_icon si es flotante.
  • submit_text y success_msg con el texto final.
  • redirect_url cargada si querés mandar a una página de gracias.
  • El <div class="koru-contact-form"> está en la página, también si es flotante.
  • data-website-id del snippet coincide con el sitio asignado al formulario.
  • data-custom-data es el Form ID de este formulario.
  • La app figura activa para ese sitio en el panel de Koru.
  • Envío de prueba hecho: aparece en el panel y llegan los emails.
  • Una sola instancia del snippet por página.

Preguntas frecuentes

¿Cuánto tarda en verse un cambio de configuración?

Los cambios de campos, diseño y emails se leen del backend en cada carga de página, sin caché propia: se ven al recargar el sitio.

Lo que sí se cachea es la autorización del widget (que la app esté habilitada para ese sitio), en localStorage bajo la clave koru_widget_<WEBSITE-ID>_<APP-ID>, con TTL de 1 hora. Para forzarlo antes, desde la consola del navegador:

localStorage.removeItem('koru_widget_TU-WEBSITE-ID_TU-APP-ID');
location.reload();

Aparte, el archivo del widget se sirve con caché de navegador de 1 hora y caché de CDN larga; eso afecta actualizaciones del código del widget, no tu configuración.

¿Afecta la performance del sitio?

No. El script es async: no bloquea el render ni el parseo del HTML. Todo el trabajo es client-side y arranca después de DOMContentLoaded — una request de autorización a Koru y otra de configuración al backend, y recién entonces se monta el formulario. El bundle es autocontenido (incluye React y ReactDOM), así que no depende de que el sitio tenga React ni de otra app de Koru. En modo Inline, mientras tanto se muestra un spinner dentro del contenedor.

No aparece nada en la página. ¿Qué reviso?

En orden, mirando la consola del navegador:

Falta el contenedor

[ContactFormWidget] No container found. Please add <div class="koru-contact-form"></div> to your page. → Agregá el <div>, también en modo flotante.

Faltan atributos

[Widget SDK] Missing required data attributesdata-website-id, data-app-id y data-app-manager-url son obligatorios los tres.

App no autorizada o inactiva en Koru

El SDK loguea Widget not authorized y no renderiza. Verificá que la app esté activa para ese sitio en el panel de Koru y que el Website ID sea el correcto.

Error de configuración

Se muestra el cartel rojo No se pudo cargar la configuración del formulario. Casos típicos: el data-custom-data no corresponde a ningún formId (404 Form not found), el formulario fue eliminado (404 Form deleted), o el data-website-id no coincide con el sitio asignado al formulario en el panel (403 Unauthorized website ID).

Dos formularios en la misma página

Solo se monta uno; separalos en páginas distintas.

El formulario se ve pero al enviar da error

El envío responde 404 Form not found or inactive si el formulario está inactivo o eliminado.

Hay un proceso automático diario (00:00 UTC) que desactiva (isActive = 0) todos los formularios cuyo sitio ya no existe en Koru. Si el sitio se dio de baja o se recreó con otro ID, reasigná el sitio al formulario y reactivalo.

Llega el email al administrador pero no la autorespuesta

Tres condiciones tienen que cumplirse: autoresponder_enabled activado, el formulario tiene un campo de tipo email, y el usuario lo completó. El detector toma el primer campo type: "email" — un campo de texto llamado "Email" no alcanza.

No llega ningún email pero el envío aparece en el panel

Es el comportamiento esperado cuando admin_email está vacío. El envío se guarda siempre; el email es un proceso posterior e independiente, y su resultado queda registrado en el log del envío. Si admin_email está cargado y aun así no llega, revisá spam y confirmá el estado del envío en el panel.

Recibo envíos vacíos o sospechosos

El widget incluye un honeypot: un campo oculto _trap. Si un bot lo completa, el envío se marca como spam, se archiva y no dispara emails, mientras el navegador ve la pantalla de éxito. Esos registros quedan en el panel marcados como spam.

Las opciones de un select no aparecen

options es un string separado por comas (Consulta, Soporte, Ventas), no una lista de objetos. Cada opción se recorta de espacios y se usa como label y value.

El formulario no toma los colores ni la tipografía de mi tienda

Es intencional: el widget aplica sus propios estilos inline (fondo blanco, bordes grises, tipografía Inter con fallback a la del sistema) y solo el color de acento es configurable. No hereda estilos del theme ni expone un archivo de override.

¿Puedo mostrar el mismo formulario en varias páginas?

Sí: el mismo snippet en cada página. La restricción es una instancia por página, no un formulario por sitio.

Datos útiles al pedir soporte

Informá el Website ID, el App ID, el Form ID, la URL de la página donde pasa, el horario aproximado y el mensaje que ves en la consola del navegador. No hace falta —ni conviene— mandar datos personales de los envíos.