En el momento en que un formulario de contacto necesita responder a la persona que lo completó, las notificaciones nativas de Webflow llegan a su límite y SendGrid puede entrar en acción para ayudar.
El sistema de formularios integrado de Webflow recopila envíos y reenvía un correo electrónico en texto plano al propietario del sitio. Eso funciona para flujos sencillos de "notifícame". Sin embargo, en el momento en que necesitas un correo de confirmación con tu marca para el usuario, validación de campos en el servidor o lógica de enrutamiento que envíe diferentes consultas a distintas bandejas de entrada, has superado lo que las notificaciones nativas pueden hacer.
Personalizar esos correos de notificación solo te lleva hasta cierto punto, como cubrí en mi guíasobre cómo personalizar los envíosde formularios de Webflow. El límite real es que el gestor de formularios de Webflow no te da control sobre el cuerpo del correo electrónico que se envía al remitente.
En esta guía, crearemos un sistema de formulario de contacto completo. Al final, tendrás:
- Un componente de formulario en React con entradas controladas y estados de carga
- Un gestor de rutas en el servidor con validación por campo
- Un campo honeypot para el filtrado básico de bots, envíos dobles de correos HTML (una confirmación para el usuario y una alerta estructurada para tu equipo), y la configuración CORS que tu gestor de rutas necesita cuando el formulario reside en un sitio de Webflow independiente que llama a tu endpoint de Webflow Cloud.
¿Qué necesitas para crear un formulario de contacto con SendGrid en Webflow Cloud?
Necesitas una aplicación Next.js en Webflow Cloud, una cuenta de SendGrid con una identidad de remitente verificada y tres variables de entorno. La configuración completa toma aproximadamente 15 minutos antes de que escribas el código de la aplicación.
Aquí tienes todo lo que debes tener listo antes de empezar.
Una aplicación Next.js en Webflow Cloud con un gestor de rutas App Router
Necesitas un proyecto de Next.js desplegado en Webflow Cloud o, como mínimo, inicializado localmente con la CLI de Webflow. La guía de inicio de Webflow Cloud explica cómo crear un proyecto a partir de una plantilla inicial y realizar el primer despliegue.
El proyecto necesita al menos un directorio de gestor de rutas App Router -- una app/api/ carpeta -- donde residirá el endpoint del formulario de contacto.
Si estás creando este formulario como parte de una aplicación más grande que incluye autenticación o pagos, la guía sobre cómo crear una aplicación full-stack en Webflow Cloud con Supabase, Auth0 y Stripe cubre cómo integrar múltiples servicios en el mismo proyecto.
Tres variables de entorno
Necesitas tres valores en tu .env.local archivo para el desarrollo local y en la configuración de variables de entorno de Webflow Cloud para producción:
SENDGRID_API_KEY=SG.xxxxxxxxxxxxxxxxxxxx
SENDGRID_FROM_EMAIL=hello@yourdomain.com
CONTACT_FORM_TO_EMAIL=team@yourdomain.com
SENDGRID_FROM_EMAIL es la dirección que aparece como remitente en todos los correos que envía este formulario de contacto. Debe coincidir con tu identidad de remitente o dominio verificado en SendGrid. CONTACT_FORM_TO_EMAIL es la bandeja de entrada de tu equipo, donde llega la notificación interna por cada nuevo envío.
Ninguno de estos necesita un NEXT_PUBLIC_ prefijo -- todas las operaciones de SendGrid en esta guía se ejecutan en el servidor dentro de los gestores de rutas, nunca en el navegador.
Una vez que estén listos, puedes continuar con la integración.
6 pasos para crear un formulario de contacto con envío de correos mediante SendGrid en Webflow Cloud
La implementación se divide en tres partes diferenciadas:
- Un asistente tipado
lib/sendgrid.tsque envuelve la API REST Mail Send v3 de SendGrid confetch() - Un manejador de rutas en
app/api/contact/route.tsque valida los campos y dispara el envío de dos correos - Un componente de formulario en React con estados de carga y error
La configuración de CORS en el manejador de rutas es lo que permite que el formulario sea invocable desde un sitio de Webflow independiente que apunte a tu endpoint de la API de Webflow Cloud.
Vamos paso a paso.
1. Añade tus credenciales de SendGrid como variables de entorno
Para el desarrollo local, añade las tres variables a en la raíz de tu proyecto Next.js. Confirma que .env.local .env.local aparece en tu .gitignore antes de continuar; subir claves de API al control de versiones es uno de los errores de desarrollo más comunes y costosos que veo en proyectos en producción:
# .env.local
SENDGRID_API_KEY=SG.xxxxxxxxxxxxxxxxxxxx
SENDGRID_FROM_EMAIL=hello@yourdomain.com
CONTACT_FORM_TO_EMAIL=team@yourdomain.com
Para producción, navega a tu panel de control de Webflow Cloud, abre el proyecto, selecciona el entorno y haz clic en Variables de entorno. Añade cada variable y activa la opción Secreto para SENDGRID_API_KEY.
Los secretos en Webflow Cloud se cifran en reposo y se enmascaran en los registros de compilación y en la interfaz del panel de control; nunca se exponen en la salida del despliegue.
2. Crea el asistente de fetch para SendGrid
En lugar de importar @sendgrid/mail, que no funciona en Cloudflare Workers debido a su dependencia de node:http, llama directamente al endpoint REST Mail Send v3 de SendGrid con fetch().
El entorno de ejecución de Workers admite fetch() de forma nativa, lo que hace que este enfoque sea compatible y más sencillo que el SDK: menos código y cero dependencias adicionales.
Crea lib/sendgrid.ts en tu proyecto:
// lib/sendgrid.ts
export type EmailContent = {
to: string
replyTo?: string
subject: string
text: string
html: string
}
export async function sendEmail(content: EmailContent): Promise<void> {
const apiKey = process.env.SENDGRID_API_KEY
const fromEmail = process.env.SENDGRID_FROM_EMAIL
if (!apiKey) throw new Error('Missing SENDGRID_API_KEY environment variable')
if (!fromEmail) throw new Error('Missing SENDGRID_FROM_EMAIL environment variable')
const body = {
personalizations: [{ to: [{ email: content.to }] }],
from: { email: fromEmail },
reply_to: content.replyTo ? { email: content.replyTo } : undefined,
subject: content.subject,
content: [
{ type: 'text/plain', value: content.text },
{ type: 'text/html', value: content.html },
],
}
const response = await fetch('https://api.sendgrid.com/v3/mail/send', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
})
if (!response.ok) {
const errorBody = await response.text()
throw new Error(`SendGrid API error ${response.status}: ${errorBody}`)
}
}
Observa que apiKey y fromEmail se leen dentro del cuerpo de la función en lugar de en el nivel superior del módulo. Este es el patrón requerido para Webflow Cloud. Vale la pena destacar el campo reply_to: configurarlo con la dirección de correo del remitente permite que tu equipo responda directamente a los envíos del formulario de contacto desde su cliente de correo sin tener que copiar la dirección manualmente.
Para un formulario de contacto, este es casi siempre el comportamiento correcto; lo configuro por defecto en todos mis proyectos.
Un envío exitoso devuelve un HTTP 202 Accepted con un cuerpo vacío. La función lanza un error ante cualquier estado que no sea OK para que los llamadores puedan capturar errores con un estándar. El patrón try/catch for-developers de devolver objetos de error estructurados en lugar de lanzar excepciones también es válido, pero lanzar errores es más sencillo y coherente con la forma en que el manejador de rutas del siguiente paso gestiona los errores.
Si estás creando otros flujos de correo junto con el formulario de contacto (confirmaciones de pedido, restablecimiento de contraseñas o notificaciones), la guía sobre el envío de correos transaccionales desde Webflow Cloud con SendGrid, Postmark y Twilio cubre patrones de asistencia basados en fetch adicionales para cada servicio.
3. Crea el manejador de rutas de la API para el formulario de contacto
Crea app/api/contact/route.ts. Este manejador de rutas acepta una solicitud POST del formulario, valida los campos entrantes, dispara dos correos en paralelo (una confirmación para quien envía el formulario y una notificación interna para tu equipo) y devuelve una respuesta JSON estructurada que el componente del formulario utiliza para mostrar estados de éxito o error.
// app/api/contact/route.ts
export const runtime = 'edge'
import { NextRequest, NextResponse } from 'next/server'
import { sendEmail } from '@/lib/sendgrid'
type ContactPayload = {
name: string
email: string
message: string
honeypot?: string
}
type FieldErrors = {
name?: string
email?: string
message?: string
}
function validatePayload(payload: ContactPayload): FieldErrors {
const errors: FieldErrors = {}
if (!payload.name || payload.name.trim().length < 2) {
errors.name = 'Name must be at least 2 characters.'
}
if (!payload.email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(payload.email)) {
errors.email = 'A valid email address is required.'
}
if (!payload.message || payload.message.trim().length < 10) {
errors.message = 'Message must be at least 10 characters.'
}
return errors
}
const ALLOWED_ORIGINS = (process.env.ALLOWED_ORIGINS ?? '').split(',').filter(Boolean)
function corsHeaders(origin: string | null): Record<string, string> {
const allowed =
origin && (ALLOWED_ORIGINS.length === 0 || ALLOWED_ORIGINS.includes(origin))
? origin
: ''
return {
'Access-Control-Allow-Origin': allowed || 'null',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
}
}
export async function OPTIONS(request: NextRequest) {
const origin = request.headers.get('origin')
return new NextResponse(null, { status: 204, headers: corsHeaders(origin) })
}
export async function POST(request: NextRequest) {
const origin = request.headers.get('origin')
const headers = corsHeaders(origin)
const payload = (await request.json()) as ContactPayload
// Honeypot check: if this field is filled, it's a bot.
if (payload.honeypot) {
return NextResponse.json({ success: true }, { headers })
}
const errors = validatePayload(payload)
if (Object.keys(errors).length > 0) {
return NextResponse.json({ errors }, { status: 422, headers })
}
const { name, email, message } = payload
const toEmail = process.env.CONTACT_FORM_TO_EMAIL
if (!toEmail) {
return NextResponse.json({ error: 'Server configuration error.' }, { status: 500, headers })
}
try {
await Promise.all([
// Confirmation to submitter
sendEmail({
to: email,
subject: `Thanks for reaching out, ${name}`,
text: `Hi ${name},\n\nWe received your message and will respond within one business day.\n\nYour message:\n${message}`,
html: `
<p>Hi ${name},</p>
<p>Thanks for reaching out. We received your message and will respond within one business day.</p>
<blockquote style="border-left:3px solid #ccc;padding-left:12px;color:#555;">
${message.replace(/\n/g, '<br>')}
</blockquote>
`,
}),
// Internal notification to team
sendEmail({
to: toEmail,
replyTo: email,
subject: `New contact form submission from ${name}`,
text: `Name: ${name}\nEmail: ${email}\nMessage:\n${message}`,
html: `
<h2>New Contact Form Submission</h2>
<p><strong>Name:</strong> ${name}</p>
<p><strong>Email:</strong> <a href="mailto:${email}">${email}</a></p>
<p><strong>Message:</strong></p>
<blockquote style="border-left:3px solid #ccc;padding-left:12px;">
${message.replace(/\n/g, '<br>')}
</blockquote>
`,
}),
])
return NextResponse.json({ success: true }, { headers })
} catch (error) {
console.error('Contact form send error:', error)
return NextResponse.json(
{ error: 'Failed to send. Please try again.' },
{ status: 500, headers }
)
}
}
Aquí ocurren algunas cosas que vale la pena analizar.
(1) Primero, export const runtime = 'edge' es necesario al principio. Webflow Cloud utiliza el adaptador OpenNext Cloudflare para ejecutar Next.js en el entorno de ejecución de Workers, y esta directiva asegura que el controlador de rutas se compile y despliegue correctamente.
Sin ella, es posible que los controladores de rutas no se compilen adecuadamente para el entorno Workers Edge.
(2) Segundo, Promise.all dispara ambos envíos de correo en paralelo. Para un formulario de contacto, la confirmación del usuario y la notificación al equipo son operaciones independientes; no hay razón para esperar a que una termine antes de iniciar la otra. Ejecutarlas simultáneamente reduce el tiempo total de envío casi a la mitad, lo cual es importante para la velocidad percibida de respuesta del formulario.
(3) Tercero, la comprobación del honeypot devuelve { success: true } en lugar de un error cuando se activa. Esto es intencional: devolver una respuesta de éxito a un bot no da señales de que el honeypot se activó, lo que dificulta que los bots sofisticados lo detecten y eludan.
Añade a tus variables de entorno si el formulario se llamará desde un sitio de Webflow independiente. Deja ALLOWED_ORIGINS=https://yoursite.webflow.io,https://yourdomain.comALLOWED_ORIGINS vacío para permitir cualquier origen (aceptable durante el desarrollo, pero no recomendado para formularios de contacto en producción).
4. Añade validación del lado del servidor y protección contra spam
La lógica de validación reside dentro de la función en el controlador de rutas, pero los patrones regex para el correo electrónico y el nombre merecen una explicación. El regex de correo electrónico validatePayload^[^\s@]+@[^\s@]+\.[^\s@]+$ es deliberadamente sencillo: detecta la gran mayoría de direcciones mal formadas sin la fragilidad y complejidad de una validación que cumpla con la norma RFC 5321, la cual suele marcar como inválidas direcciones legítimas.
Para un formulario de contacto, el objetivo es evitar errores de escritura accidentales, no aplicar la especificación de correo electrónico de forma exhaustiva. Si la entregabilidad a una dirección específica es importante, la verdadera prueba es intentar enviar el correo, no un regex.
El campo honeypot es una entrada oculta que los visitantes humanos nunca ven ni rellenan, pero los bots lo hacen con frecuencia porque analizan el DOM y completan cada entrada. La implementación requiere un campo invisible coincidente en el componente de tu formulario, lo cual se cubre en el siguiente paso.
Utilizo combinado con en el contenedor del campo; esto mantiene el campo invisible para los lectores de pantalla y lo elimina del orden de tabulación, evitando que el autorrelleno del navegador lo active. Nombrar el campo de forma plausible para un bot pero irrelevante para los humanos, como o aria-hidden="true"style={{ display: 'none' }}websitecompany, aumenta la tasa de detección.
Los honeypots detienen de forma fiable a los bots sencillos, pero los scrapers sofisticados que renderizan JavaScript y imitan el comportamiento humano pueden evitarlos. Para formularios de contacto con mucho tráfico o que son objetivo de spam, combinar un honeypot con reCAPTCHA ofrece una cobertura más sólida.
También puedes leer sobre la técnica de honeypot propia de Webflow para filtrar envíos de spam en formularios y compararla con cómo lo gestionan los formularios nativos de Webflow. También añado una limitación de tasa básica a nivel de infraestructura utilizando la limitación de tasa integrada de Cloudflare Workers cuando los proyectos son especialmente propensos al spam, pero para la mayoría de los formularios de contacto, un honeypot más reCAPTCHA es suficiente.
5. Crea el componente de formulario de contacto en React
El componente del formulario de contacto gestiona tres estados: predeterminado (formulario inactivo), cargando (envío en curso) y resultado (mensaje de éxito o error). Usar un patrón de componente controlado aquí te permite mostrar errores por campo provenientes de la respuesta de validación del servidor.
Crea :components/ContactForm.tsx
// components/ContactForm.tsx
'use client'
import { FormEvent, useState } from 'react'
type FieldErrors = { name?: string; email?: string; message?: string }
type FormState = { status: 'idle' | 'loading' | 'success' | 'error'; errors?: FieldErrors; message?: string }
const API_URL = process.env.NEXT_PUBLIC_CONTACT_API_URL ?? '/api/contact'
export function ContactForm() {
const [state, setState] = useState<FormState>({ status: 'idle' })
async function handleSubmit(e: FormEvent<HTMLFormElement>) {
e.preventDefault()
setState({ status: 'loading' })
const form = e.currentTarget
const data = {
name: (form.elements.namedItem('name') as HTMLInputElement).value,
email: (form.elements.namedItem('email') as HTMLInputElement).value,
message: (form.elements.namedItem('message') as HTMLTextAreaElement).value,
honeypot: (form.elements.namedItem('honeypot') as HTMLInputElement).value,
}
try {
const res = await fetch(API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
const json = await res.json()
if (res.ok) {
setState({ status: 'success' })
form.reset()
} else if (res.status === 422 && json.errors) {
setState({ status: 'error', errors: json.errors })
} else {
setState({ status: 'error', message: json.error ?? 'Something went wrong. Please try again.' })
}
} catch {
setState({ status: 'error', message: 'Network error. Please check your connection and try again.' })
}
}
return (
<form onSubmit={handleSubmit} noValidate>
{/* Honeypot field -- hidden from humans, visible to bots */}
<div aria-hidden="true" style={{ display: 'none' }}>
<label htmlFor="honeypot">Leave this field blank</label>
<input type="text" id="honeypot" name="honeypot" tabIndex={-1} autoComplete="off" />
</div>
<div>
<label htmlFor="name">Name</label>
<input type="text" id="name" name="name" required autoComplete="name" />
{state.errors?.name && <p role="alert">{state.errors.name}</p>}
</div>
<div>
<label htmlFor="email">Email</label>
<input type="email" id="email" name="email" required autoComplete="email" />
{state.errors?.email && <p role="alert">{state.errors.email}</p>}
</div>
<div>
<label htmlFor="message">Message</label>
<textarea id="message" name="message" required rows={5} />
{state.errors?.message && <p role="alert">{state.errors.message}</p>}
</div>
{state.status === 'success' && (
<p role="status">Your message was sent. We will be in touch within one business day.</p>
)}
{state.status === 'error' && state.message && (
<p role="alert">{state.message}</p>
)}
<button type="submit" disabled={state.status === 'loading'}>
{state.status === 'loading' ? 'Sending...' : 'Send message'}
</button>
</form>
)
}
La variable de entorno te permite apuntar el formulario a una URL diferente en distintos entornos. En desarrollo, el valor predeterminado se resuelve contra NEXT_PUBLIC_CONTACT_API_URL/api/contactlocalhost:3000.
En producción, dentro del mismo dominio, se resuelve en tu aplicación de Webflow Cloud desplegada. Si el formulario reside en un sitio de Webflow independiente que llama a tu aplicación de Webflow Cloud de forma cruzada (cross-origin), configura esto con la URL completa de tu endpoint de contacto de Webflow Cloud: por ejemplo, https://app.yourdomain.com/api/contact.
El en los párrafos de error y role="alert"role="status" en el mensaje de éxito aseguran que los lectores de pantalla anuncien los cambios de estado sin necesidad de mover el foco. Son pequeños detalles, pero los formularios de contacto son de los componentes más sensibles a la accesibilidad en la mayoría de los sitios, ya que son la ruta de conversión directa para los visitantes que necesitan ayuda.
6. Conecta el formulario a tu sitio de Webflow
Si tu componente de formulario de contacto reside dentro de tu aplicación Next.js de Webflow Cloud, ya has terminado: el formulario se renderiza del lado del servidor en el mismo origen que el controlador de rutas, no se necesita configuración CORS, y solo tienes que incrustar la página o la ruta de montaje de Webflow Cloud en tu sitio de Webflow.
La mayoría de los proyectos de Webflow Cloud se montan bajo una ruta como /app en el dominio principal del sitio de Webflow, lo que mantiene todo bajo el mismo origen.
Si el formulario se encuentra en otro lugar (un formulario nativo de Webflow en tu sitio principal o una página independiente que llama a tu API de Webflow Cloud de forma cruzada), se necesitan dos cambios.
Añadir los dominios de tu sitio de Webflow a laALLOWED_ORIGINSvariable de entorno
Primero, añade los dominios de tu sitio de Webflow a la ALLOWED_ORIGINS variable de entorno en tu proyecto de Webflow Cloud:
ALLOWED_ORIGINS=https://yoursite.webflow.io,https://yourdomain.com
Segundo, añade JavaScript personalizado al evento de envío de tu formulario de Webflow para interceptar la acción nativa y enviarla a tu endpoint de Webflow Cloud. Esto reemplaza lo que Webflow haría normalmente con los datos del formulario.
Si no estás ejecutando el componente de React en una página alojada en Webflow Cloud, una llamada fetch desde el elemento Embed de Webflow lo gestiona correctamente. Para los equipos que gestionan el enrutamiento de formularios a CRM desde formularios nativos de Webflow, el enfoque para vincular formularios de Webflow a HubSpot usando la aplicación nativa sigue el mismo patrón de enrutamiento que para los formularios nativos.
Despliegue de tu Webflow Cloud
Despliega tu aplicación de Webflow Cloud con webflow cloud deploy una vez que todos los cambios se hayan confirmado. Verifica que el despliegue se realizó correctamente, abre la URL desplegada, envía el formulario de contacto con tu propio correo electrónico y un mensaje de prueba, y confirma que tanto la confirmación del usuario como la notificación al equipo lleguen correctamente.
Revisa la sección de Actividad de tu panel de SendGrid para ver el registro de ambos envíos, cada uno con su estado de entrega y cualquier evento de rebote o filtro de spam.
¿Qué causa que falle la entrega de correos electrónicos de formularios de contacto en Webflow Cloud?
La mayoría de los fallos en los formularios de contacto en Webflow Cloud se deben a una de estas cuatro causas principales:
- Se está importando el paquete
@sendgrid/mailen lugar de usar el asistente fetch - Las variables de entorno se están leyendo al nivel superior del módulo
- Falta una
export const runtime = 'edge'directiva en el manejador de rutas - Una identidad de remitente que no ha sido verificada en SendGrid
Cada uno de estos errores ocurre silenciosamente de formas distintas, por lo que pueden ser difíciles de diagnosticar.
Aquí te explicamos cómo reconocer cada uno y cómo solucionarlo.
SendGrid devuelve un error 401 Unauthorized tras el despliegue, pero no en desarrollo
Esto indica que process.env.SENDGRID_API_KEY se lee durante la inicialización del módulo en lugar de en el cuerpo de una función. En desarrollo, next dev carga las variables de entorno al inicio, por lo que las lecturas a nivel de módulo devuelven el valor correcto.
En el entorno de producción de Webflow Cloud, las variables de entorno se inyectan en el momento de la solicitud: el módulo se carga, process.env.SENDGRID_API_KEY se evalúa como undefined, y cada llamada posterior pasa un token Bearer vacío a SendGrid.
La solución consiste en mover todas las process.env lecturas dentro de los cuerpos de las funciones. El asistente del Paso 2 lo hace correctamente: tanto apiKey como fromEmail se leen dentro de sendEmail(). Si ves un error 401 a pesar de tener la variable configurada en tu panel de Webflow Cloud, revisa cada archivo que importe o utilice process.env valores de SendGrid y confirma que se lean dentro de funciones.
Además, verifica que la clave API tenga permisos de acceso completos para Mail Send. Una clave API recién generada viene por defecto con Acceso Restringido, y es fácil pasar por alto el interruptor de Mail Send durante la creación.
Los envíos del formulario de contacto funcionan, pero los correos llegan a spam
El filtrado de spam casi siempre se debe a la falta de autenticación del dominio. Cuando envías desde un dominio que no ha sido autenticado con SendGrid (mediante registros CNAME de DKIM y SPF), proveedores de correo como Gmail y Outlook no tienen pruebas criptográficas de que SendGrid esté autorizado para enviar en tu nombre. Por lo tanto, aplican una puntuación de spam en consecuencia.
La solución es completar la autenticación del dominio en SendGrid en Configuración > Autenticación del remitente. Con la Seguridad Automatizada activada (la opción predeterminada), SendGrid genera cuatro registros DNS: tres registros CNAME para el enrutamiento de correo y la marca de enlaces, además de un registro TXT para DMARC.
Después de añadirlos, haz clic en Verificar en el panel de SendGrid. La propagación del DNS suele completarse en un plazo de 5 a 30 minutos. Si la verificación falla, espera 10 minutos e inténtalo de nuevo. Una vez que la autenticación esté activa, la seguridad automatizada de SendGrid rotará las claves DKIM sin tiempo de inactividad ni interrupciones en la entregabilidad.
Si estás enviando correos de confirmación de pedido desde Webflow con SendGrid desde el mismo dominio, una configuración de autenticación de dominio cubre ambos, ya que lo que se autentica es el dominio de envío y no las claves API o direcciones de correo electrónico individuales.
El honeypot está bloqueando envíos de usuarios reales
El autocompletado del navegador es la causa más común de los falsos positivos. Si la función de autocompletado de un navegador detecta el campo honeypot al escanear los nombres de los campos, lo rellena junto con los campos legítimos, lo que provoca que los envíos reales se descarten silenciosamente.
Dos prácticas evitan esto de forma fiable.
(1) Primero, establece autoComplete="off" en el campo honeypot (como se muestra en el Paso 5).
(2) Segundo, dale al campo honeypot un nombre que parezca algo que un bot querría rellenar, como website o company , pero que ningún perfil de autocompletado de navegador rellenaría basándose en tus otros campos del formulario.
Si los falsos positivos persisten, cambia el nombre del campo honeypot por algo más oscuro. También he visto problemas cuando las extensiones de navegador de los gestores de contraseñas escanean formularios de forma agresiva y rellenan cualquier campo visible para el DOM.
Envolver el honeypot en un contenedor display: none (como se muestra en el componente) soluciona la mayoría de los casos, pero una regla CSS position: absolute; left: -9999px adicional como medida de seguridad extra elimina los casos excepcionales restantes con ciertas superposiciones de accesibilidad.
Próximos pasos
Añadir seguimiento de errores a tus manejadores de rutas de Webflow Cloud lleva menos de 10 minutos una vez que el formulario de contacto funciona. Nuestra guía sobre cómo añadir el seguimiento de errores de Sentry a una aplicación de Webflow Cloud cubre la configuración exacta: el asistente de Next.js de Sentry, la exportación onRequestError en instrumentation.ts, y cómo añadir contexto estructurado a los errores para que cada excepción capturada te indique qué ruta falló y qué contenía la solicitud.
Para los equipos que necesitan que los datos del formulario de contacto fluyan hacia un CRM o una herramienta de gestión de proyectos en lugar de quedarse en un correo de notificación, consulta cómo conectar formularios de Webflow a HubSpot mediante Zapier.
Explora Webflow + SendGrid para ver cómo SendGrid se conecta con el CMS y los datos de formularios de Webflow.
Preguntas frecuentes
¿Puedo usar@sendgrid/mailen Webflow Cloud en lugar del asistente de fetch?
No. El paquete @sendgrid/mail depende de los módulos the node:http y node:https , que Cloudflare Workers no proporciona. Importarlo provoca un error de compilación o de ejecución en Webflow Cloud, incluso si funciona correctamente en next dev local, porque el desarrollo local se ejecuta en Node.js completo. El asistente basado en fetch del Paso 2 reemplaza el SDK por completo y, de hecho, requiere menos código: la API REST v3 de SendGrid acepta un POST JSON simple, lo que hace que la capa del SDK sea innecesaria para los casos de uso de formularios de contacto.
¿Cómo añado reCAPTCHA a un formulario de contacto de Webflow Cloud?
Añade tu clave de sitio de reCAPTCHA v3 como una NEXT_PUBLIC_ variable de entorno para que el componente de formulario del lado del cliente pueda inicializarla. En el lado del servidor, verifica el token en el manejador de rutas llamando a la API de verificación de reCAPTCHA de Google con fetch() antes de ejecutar validatePayload. Verifica la puntuación devuelta (normalmente se usa un umbral de 0.5) y rechaza los envíos con una puntuación inferior con una respuesta 422. La guía sobre cómo añadir protección contra spam de reCAPTCHA a formularios de Webflow cubre el flujo de verificación en detalle.
¿Necesito cabeceras CORS si el formulario está dentro de mi aplicación de Webflow Cloud?
No. Si el componente de formulario de contacto es un componente de React renderizado dentro de tu aplicación Next.js en Webflow Cloud y el formulario envía datos a /api/contact en el mismo origen, no se necesita configuración CORS. CORS solo se aplica cuando la página que realiza la solicitud proviene de un origen diferente al del punto final de la API. La configuración CORS en el paso 3 —la ALLOWED_ORIGINS variable y el OPTIONS manejador de preflight— solo es necesaria cuando el formulario está alojado en un sitio de Webflow independiente que llama a tu punto final de la API de Webflow Cloud desde un origen distinto.





