Una vez que Webflow Logic cerró en junio de 2025, enviar un correo electrónico transaccional adecuado desde un formulario de Webflow requería un backend real. Webflow Cloud convierte ese backend en un Route Handler; unas pocas docenas de líneas de TypeScript que llaman a la API de Postmark y envían dos correos electrónicos en el momento en que se envía un formulario.
El correo electrónico transaccional es una de las primeras cosas que los clientes me piden añadir cuando un sitio de Webflow deja de ser un simple folleto. Las notificaciones de formulario integradas de Webflow se envían al propietario del sitio, sin control sobre la dirección del remitente, la plantilla o el contenido.Webflow Logic, que gestionaba parte de esta automatización, se interrumpió el 27 de junio de 2025.
El enfoque actual es un Route Handler de Webflow Cloud App que recibe los datos del formulario, llama a la API HTTP de Postmark y envía correos electrónicos personalizados en ambas direcciones: una confirmación para quien envía el formulario y una notificación para tu equipo.
En esta guía, cubrimos cada paso: verificar tu dominio de remitente en Postmark, configurar las credenciales, escribir un asistente de correo electrónico reutilizable, crear el Route Handler y conectarlo a un formulario de Webflow con JavaScript del lado del cliente.
¿Qué necesitas para enviar correos electrónicos transaccionales desde Webflow Cloud?
Necesitas una cuenta de Postmark, un dominio de remitente verificado y una Webflow Cloud App ya estructurada antes de escribir cualquier código.
Asegúrate de completar estos requisitos previos antes de empezar:
- Una cuenta de Postmark: El plan gratuito permite enviar 100 correos al mes, lo cual es suficiente para el desarrollo y la producción de bajo volumen.
- Un dominio de tu propiedad con acceso DNS: Postmark requiere que añadas registros DNS (DKIM + Return-Path) para verificar tu dominio de envío.
- UnaWebflow Cloud App: Ejecuta
webflow auth login && webflow cloud initsi aún no lo has hecho. - Un sitio de Webflow con al menos un formulario que requiera gestión de correo electrónico personalizada.
Algo que debes saber antes de empezar: el paquete oficial de postmark npm no funciona en Cloudflare Workers. La propia documentación de Cloudflare lo indica directamente: "La biblioteca de JavaScript de Postmark no es compatible actualmente con Workers". Sin embargo, la API HTTP funciona perfectamente mediante fetch(). Eso es lo que utiliza esta guía.
5 pasos para enviar correos electrónicos transaccionales desde Webflow Cloud con Postmark
Postmark se encarga de la entrega. La Webflow Cloud App se encarga de la validación, el enrutamiento y la configuración de la respuesta. Estos pasos construyen la integración en el siguiente orden: verificación del dominio del remitente, configuración de credenciales, un asistente de correo electrónico reutilizable y un Route Handler al que se envía el formulario de Webflow.
1. Verifica tu dominio de remitente en Postmark
Postmark bloquea los envíos desde dominios no verificados. Este paso no se puede omitir y es la razón más común por la que falla el primer envío. Configúralo antes de escribir cualquier código.
Inicia sesión en Postmark y navega a Sender Signatures → Add Domain or Signature → Add Domain. Introduce tu dominio y haz clic en Verify Domain.
Postmark mostrará dos registros DNS para añadir:
- Un registro DKIM (tipo TXT)
- Un registro Return-Path (tipo CNAME)
Añade ambos registros a tu proveedor de DNS. En Cloudflare, ve a DNS → Records → Add record y copia cada valor exactamente. Una vez que los registros se propaguen (normalmente en menos de 10 minutos en Cloudflare), vuelve a Postmark y haz clic en los botones Verify.
Con el dominio verificado, obtén el token de API de tu servidor. Ve a Servers → My First Server → API Tokens y copia el token del servidor. Este valor se incluye en la X-Postmark-Server-Tokencabecera de autenticación para cada solicitud a la API.
Punto de control: Desde el panel de Postmark, usa el botón Test para enviar un correo de prueba. Si llega, el dominio está verificado y el token funciona.
2. Añade las credenciales de Postmark a tu entorno de Webflow Cloud
Tu aplicación en la nube necesita dos variables de entorno.
Añádelas a .env.local para el desarrollo local:
# .env.local
POSTMARK_SERVER_TOKEN=your_server_token_here
POSTMARK_FROM_EMAIL=hello@yourdomain.com
OWNER_EMAIL=you@yourdomain.com
En la configuración de tu sitio Webflow, abre Webflow Cloud, selecciona tu entorno y añade las mismas variables en Environment Variables.
Para pruebas locales sin enviar correos reales, Postmark ofrece un token de prueba especial: POSTMARK_API_TEST. Cambia tu token real por este valor localmente para validar el formato de la solicitud sin activar un envío real.
Punto de control: Ejecuta next dev localmente y confirma que process.env.POSTMARK_SERVER_TOKEN se resuelve con tu token.
3. Crea el asistente de correo de Postmark
Crea lib/postmark.ts en tu Cloud App. Este módulo gestiona la llamada HTTP al endpoint /email de Postmark y da forma a la solicitud según el formato que espera la API.
Esto es lo que debes hacer:
// lib/postmark.ts
const POSTMARK_API = 'https://api.postmarkapp.com'
interface EmailParams {
to: string
from: string
subject: string
htmlBody: string
textBody?: string
replyTo?: string
tag?: string
messageStream?: string
}
interface PostmarkResponse {
ErrorCode: number
Message: string
MessageID: string
SubmittedAt: string
To: string
}
export async function sendEmail(params: EmailParams): Promise<PostmarkResponse> {
const token = process.env.POSTMARK_SERVER_TOKEN
if (!token) throw new Error('POSTMARK_SERVER_TOKEN is not set')
const res = await fetch(`${POSTMARK_API}/email`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'X-Postmark-Server-Token': token,
},
body: JSON.stringify({
From: params.from,
To: params.to,
Subject: params.subject,
HtmlBody: params.htmlBody,
TextBody: params.textBody ?? params.htmlBody.replace(/<[^>]+>/g, ''),
ReplyTo: params.replyTo,
Tag: params.tag,
TrackOpens: true,
MessageStream: params.messageStream ?? 'outbound',
}),
})
if (!res.ok) {
const error = (await res.json()) as PostmarkResponse
throw new Error(`Postmark error ${error.ErrorCode}: ${error.Message}`)
}
return res.json() as Promise<PostmarkResponse>
}
Mantengo la llamada a Postmark en un asistente dedicado en lugar de incluirla directamente en el Route Handler. Cuando la misma aplicación envía varios tipos de correo (confirmación de contacto, recibo de pedido, restablecimiento de contraseña), cada Route Handler importa sendEmail() con sus propios parámetros. Así evitas duplicar la lógica de fetch en varios archivos.
El TextBody de reserva elimina automáticamente las etiquetas HTML para generar una versión en texto plano. Para producción, escribe un cuerpo de texto plano adecuado: los clientes de correo que solo muestran texto (incluyendo muchos filtros de spam corporativos) ofrecen una mejor experiencia que el HTML eliminado.
Punto de control: Desde un script de prueba rápido, llama a sendEmail() con POSTMARK_SERVER_TOKEN configurado como POSTMARK_API_TEST. Deberías recibir { ErrorCode: 0, Message: "Test job accepted" }.
4. Crea el Route Handler del formulario de contacto
Crea app/api/contact/route.ts. Este Route Handler recibe un envío de formulario en JSON, valida la entrada, envía dos correos a través de Postmark (una confirmación al remitente y una notificación al propietario del sitio) y devuelve una respuesta JSON.
Aquí tienes el contenido:
// app/api/contact/route.ts
export const runtime = 'edge'
import { sendEmail } from '@/lib/postmark'
const SENDER = process.env.POSTMARK_FROM_EMAIL ?? 'hello@yourdomain.com'
const OWNER_EMAIL = process.env.OWNER_EMAIL ?? 'you@yourdomain.com'
interface ContactPayload {
name: string
email: string
message: string
}
function isValidEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
}
export async function POST(request: Request) {
let body: ContactPayload
try {
body = (await request.json()) as ContactPayload
} catch {
return Response.json({ error: 'Invalid JSON' }, { status: 400 })
}
const { name, email, message } = body
if (!name?.trim() || !email?.trim() || !message?.trim()) {
return Response.json(
{ error: 'Name, email, and message are required' },
{ status: 422 }
)
}
if (!isValidEmail(email)) {
return Response.json({ error: 'Invalid email address' }, { status: 422 })
}
try {
// Confirmation to the submitter
const confirmation = await sendEmail({
to: email,
from: SENDER,
replyTo: OWNER_EMAIL,
subject: `Thanks for reaching out, ${name}`,
tag: 'contact-confirmation',
htmlBody: `
<p>Hi ${name},</p>
<p>Thanks for your message. I'll get back to you within one business day.</p>
<p>Here's what you sent:</p>
<blockquote>${message}</blockquote>
`,
})
// Notification to the site owner
await sendEmail({
to: OWNER_EMAIL,
from: SENDER,
replyTo: email,
subject: `New contact from ${name}`,
tag: 'contact-notification',
htmlBody: `
<p><strong>Name:</strong> ${name}</p>
<p><strong>Email:</strong> <a href="mailto:${email}">${email}</a></p>
<p><strong>Message:</strong></p>
<p>${message}</p>
`,
})
return Response.json({
success: true,
messageId: confirmation.MessageID,
})
} catch (err) {
console.error('Email send failed:', err)
return Response.json({ error: 'Failed to send email' }, { status: 502 })
}
}
El controlador valida los datos antes de llamar a Postmark. Un nombre vacío o una dirección de correo mal formada nunca llegan a la API, lo que significa que no se desperdician llamadas a la API ni hay que lidiar con respuestas 422 confusas de Postmark.
Configuro ReplyTo en el correo electrónico del propietario en la confirmación y en el correo del remitente en la notificación. De esta forma, cuando el propietario del sitio hace clic en responder en su cliente de correo, contacta directamente con quien envió el formulario.
Punto de control: Puedes probar el endpoint con cURL:
curl -X POST http://localhost:3000/app/api/contact \
-H "Content-Type: application/json" \
-d '{"name":"Test User","email":"test@example.com","message":"Hello"}'
Con POSTMARK_SERVER_TOKEN=POSTMARK_API_TEST, deberías recibir { "success": true, "messageId": "..." }. Con tu token real, revisa el registro de actividad de Postmark para confirmar que aparecen ambos correos.
5. Conecta el formulario de Webflow
Añade esto a un bloque de Código personalizado (antes de </body>) en cualquier página que tenga un formulario de contacto. El formulario necesita tres campos de entrada con los atributos data-contact-name, data-contact-email y data-contact-message, además de un elemento de estado con el data-contact-status.
Añade un atributo data-contact-form al propio elemento del formulario:
(function () {
const form = document.querySelector('[data-contact-form]')
const status = document.querySelector('[data-contact-status]')
if (!form) return
form.addEventListener('submit', async function (e) {
e.preventDefault()
const name = form.querySelector('[data-contact-name]')?.value.trim()
const email = form.querySelector('[data-contact-email]')?.value.trim()
const message = form.querySelector('[data-contact-message]')?.value.trim()
if (!name || !email || !message) {
if (status) status.textContent = 'Please fill in all fields.'
return
}
const submitBtn = form.querySelector('[type="submit"]')
if (submitBtn) submitBtn.disabled = true
if (status) status.textContent = 'Sending...'
try {
const res = await fetch('/app/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, email, message }),
})
const data = await res.json()
if (data.success) {
if (status) status.textContent = 'Message sent. Check your inbox for a confirmation.'
form.reset()
} else {
if (status) status.textContent = data.error ?? 'Something went wrong. Please try again.'
}
} catch {
if (status) status.textContent = 'Network error. Please try again.'
} finally {
if (submitBtn) submitBtn.disabled = false
}
})
})()
Desactivo el botón de envío mientras la solicitud está en curso. Sin esto, los envíos dobles mandan dos correos de confirmación a la misma dirección. El elemento data-contact-status ofrece feedback a los usuarios sin activar la recarga de página que provocaría el gestor de formularios predeterminado de Webflow.
Punto de control: Publica tu sitio de Webflow, rellena el formulario y envíalo. El mensaje de estado debería actualizarse y deberías recibir tanto el correo de confirmación como la notificación al propietario.
¿Qué causa que los envíos de correo de Postmark fallen en Webflow Cloud?
La mayoría de los fallos se agrupan en tres categorías: importar el SDK de npm cuando no debería hacerse, firmas de remitente no verificadas y direcciones de destinatario inactivas.
Aquí tienes cómo identificar cada uno y cómo solucionarlo.
El paquete npm de postmark da error al importar
Si ves "Cannot find module 'events'" o "TypeError: Class extends value undefined" cuando se carga tu gestor de rutas, es que el paquete postmark npm está instalado. Elimínalo con npm uninstall postmark y usa el asistente basado en fetch() del paso 3.
La documentación de Cloudflare Workers indica explícitamente que la biblioteca de JavaScript de Postmark no es compatible con Workers. La API HTTP cubre todas las operaciones de esta guía.
Postmark devuelve el código de error 400 o 401
ErrorCode 400significa que no existe ninguna firma de remitente en Postmark para la dirección From; aún no se ha creado. El código de error 401 significa que la firma del remitente existe, pero aún no se ha hecho clic en el correo de confirmación.
Ambos se resuelven en el panel de control de Postmark: añade la dirección From como una nueva firma de remitente o completa la verificación del dominio en Firmas de remitente. Un dominio verificado cubre cualquier dirección en ese dominio, así que hello@yourdomain.com y no-reply@yourdomain.com funcionan una vez que el dominio está verificado.
Postmark devuelve el código de error 406
El código de error 406 significa que la dirección del destinatario está inactiva. Postmark marca a los destinatarios como inactivos tras un rebote permanente o una queja de spam. Gestiono el 406 en el gestor de rutas devolviendo un error al usuario: "Esta dirección de correo ha rebotado anteriormente. Por favor, usa una dirección distinta".
Volver a enviar no funcionará. Los destinatarios inactivos requieren una reactivación manual a través del registro de actividad de Postmark.
Qué hacer después de configurar el correo transaccional en Webflow Cloud
Si prefieres realizar los envíos a través de SendGrid, se aplica la misma estructura de Route Handler y nuestra guía de formularios de contacto con SendGrid lo cubre todo, incluyendo la validación del lado del servidor y la protección contra spam mediante honeypot, temas que no se tratan en este tutorial.
Una vez que el correo funciona, el siguiente paso lógico es usar las plantillas de Postmark. En lugar de crear el HTML del correo dentro del Route Handler, la API de plantillas de Postmark te permite gestionar el contenido en su panel de control y renderizarlo con variables al momento del envío: una sendTemplate() llamada que envía datos a /email/withTemplate con { TemplateAlias: 'contact-confirmation', TemplateModel: { name, message } }. El HTML se mantiene fuera del código y puedes editarlo sin necesidad de realizar un despliegue.
Explora Webflow + Postmark para ver la lista completa de opciones de integración, o dirígete a la documentación de Webflow Cloud si estás configurando tu aplicación en la nube por primera vez.
Preguntas frecuentes
¿Puedo usar las plantillas de correo integradas de Postmark?
Sí. La API de plantillas de Postmark acepta un TemplateAlias (el identificador de la plantilla en el panel de control) y un TemplateModel (un objeto JSON con las variables a sustituir). Reemplaza sendEmail() con una sendTemplate() función que envíe datos a /email/withTemplate con { TemplateAlias: 'your-alias', TemplateModel: { name, message } }. Las plantillas se crean y editan en el panel de Postmark sin necesidad de desplegar código para actualizar el contenido.
¿Cómo disparo correos desde los formularios integrados de Webflow?
Los formularios integrados de Webflow activan un form_submission webhook cuando un visitante envía un formulario. Configura un webhook en Ajustes de Webflow → Integraciones → Webhooks con el disparador form_submission apuntando a la URL de tu Route Handler. El Route Handler recibe los datos del formulario como JSON y llama a sendEmail() exactamente como se indicó arriba. La única diferencia es la estructura de los datos entrantes: el webhook de Webflow entrega un objeto fieldData anidado en lugar de campos JSON planos.
¿Cómo envío correos a varios destinatarios?
Postmark acepta direcciones separadas por comas en los campos To, Cc y Bcc, con un límite combinado de 50 destinatarios por mensaje. Para envíos con más de 50 destinatarios, utiliza el punto de conexión por lotes de Postmark en /email/batch, que acepta hasta 500 mensajes por llamada a la API.
¿Cómo puedo probar sin enviar correos reales?
Configura POSTMARK_SERVER_TOKEN=POSTMARK_API_TEST en .env.local. Las solicitudes se envían a la API de Postmark, tu formato JSON se valida y recibes un MessageID de vuelta. No se entrega ningún correo. Esta es la forma correcta de escribir pruebas de integración para el flujo de correo sin enviarlo a bandejas de entrada reales.






