Crear el cuerpo de los correos mediante código funciona hasta que alguien te pide cambiar una frase. Las plantillas sacan el texto de tu despliegue.
La mayoría de las guías para enviar correos desde una aplicación de Webflow Cloud crean el cuerpo del mensaje en el gestor de rutas. Es un buen punto de partida, pero deja de serlo en cuanto alguien te pide cambiar una frase en el correo de confirmación.
El texto vive dentro de un despliegue. El equipo de marketing no puede tocarlo; corregir una coma requiere una solicitud de extracción (pull request), y cada formulario nuevo que añades significa otra cadena de texto acumulándose junto a la anterior.
Las plantillas de Postmark sacan ese contenido de tu código. El gestor de rutas deja de cargar palabras y empieza a gestionar datos, lo cual es una tarea mucho más pequeña y estable.
Esta guía conecta los formularios de Webflow con las plantillas de Postmark y se centra en la parte que suele fallar: el contrato entre los campos de tu formulario y los marcadores de posición de la plantilla.
¿Qué necesitas para enviar plantillas de Postmark desde formularios de Webflow?
Necesitas un servidor de Postmark con un remitente verificado, una aplicación de Webflow Cloud para recibir los envíos y un formulario de Webflow cuyos nombres de campo puedas controlar. Aquí, los nombres de los campos son lo más importante.
Prepara esto primero:
- Un dominio de envío verificado en Postmark, que es la ruta preferible: una firma de remitente por dirección requiere que alguien haga clic en un enlace de confirmación en ese buzón, y una
notifications@dirección suele estar desatendida - Tu token de servidor de Postmark, que encontrarás en la pestaña de tokens de API del servidor desde el que envías; es una credencial a nivel de servidor, no de cuenta
- Una aplicación de Webflow Cloud con Next.js, donde el gestor de rutas y el token residen en el lado del servidor
- Un formulario de Webflow con nombres de campo bien definidos, ya que esos nombres son los que asignarás a los marcadores de posición de la plantilla
Así es como cambia la cosa y por qué merece la pena:
| Qué cambia | Cuerpo creado en el código | Cuerpo creado como plantilla de Postmark |
|---|---|---|
| Editar el texto | Un cambio de código y un nuevo despliegue | Edita en Postmark y publícalo al instante |
| ¿Quién puede cambiarlo? | Cualquiera que pueda desplegar la aplicación | Cualquiera con acceso a Postmark |
| Añadir un segundo formulario | Otra cadena de cuerpo en el controlador | Otra plantilla, mismo controlador |
| Lo que envía el controlador | Asunto, cuerpo HTML y cuerpo de texto | Una referencia de plantilla y un modelo de datos |
| Encabezado y pie de página compartidos | Duplicado en cada cadena de cuerpo | Un diseño, referenciado por cada plantilla |
| Qué cambia → Cuerpo creado en código → Cuerpo creado como plantilla de Postmark |
|---|
| Editar el texto |
| Un cambio de código y un nuevo despliegue |
| Edita en Postmark y publícalo al instante |
| ¿Quién puede cambiarlo? |
| Cualquiera que pueda desplegar la aplicación |
| Cualquiera con acceso a Postmark |
| Añadir un segundo formulario |
| Otra cadena de cuerpo en el controlador |
| Otra plantilla, mismo controlador |
| Lo que envía el controlador |
| Asunto, cuerpo HTML y cuerpo de texto |
| Una referencia de plantilla y un modelo de datos |
| Encabezado y pie de página compartidos |
| Duplicado en cada cadena de cuerpo |
| Un diseño, referenciado por cada plantilla |
La fila que justifica la migración es la segunda. Todo lo demás es comodidad; trasladar el texto a un lugar donde alguien que no sea desarrollador pueda editarlo es lo que evita que tú seas el cuello de botella de las palabras.
Ten en cuenta el límite antes de planificar tus plantillas: un servidor de Postmark admite hasta 100 plantillas, y las solicitudes que superan ese límite no se procesan. Es una cifra generosa para notificaciones de formularios y conviene saberlo si planeas usar una plantilla por cliente. Una vez hecho esto, la clave está en nombrar las cosas de forma coherente en ambos sistemas.
5 pasos para enviar plantillas de Postmark desde un formulario de Webflow
La estructura consiste en un diseño, una plantilla, un token, un controlador de rutas que envía datos en lugar de palabras y un mapeo que permite que varios formularios lo compartan.
El orden importa porque la plantilla define los marcadores de posición, y estos definen lo que tu controlador debe recopilar.
1. Crea primero el diseño y luego la plantilla
En Postmark, crea un diseño (Layout) antes que la plantilla. Un diseño contiene las partes que comparten todos los mensajes: el encabezado, el pie de página y la línea legal; las plantillas hacen referencia a él en lugar de repetir estos elementos.
Postmark distingue ambos explícitamente: el TemplateType de una plantilla es Standard o Layout, y una plantilla estándar que utiliza uno informa el alias del diseño en su campo LayoutTemplate.
Hacer esto bien desde el principio marca la diferencia entre cambiar el pie de página una sola vez o tener que hacerlo en cada plantilla que hayas creado.
Luego, crea la plantilla y asígnale un alias. Postmark te permite enviar mediante un TemplateId numérico o un TemplateAlias, y necesitas uno de los dos. Prefiere el alias: es un nombre que tú eliges, se lee claramente en tu código y sobrevive a las recreaciones, a diferencia de un ID asignado automáticamente.
Escribe tus marcadores de posición a medida que avanzas, usando la sintaxis de doble llave de Postmark, de modo que una línea de asunto se vea algo así como New inquiry from {{name}}.
Una nota de seguridad mientras estás en el editor: esta compilación canaliza la entrada del formulario hacia un correo electrónico HTML. La creación de plantillas de Postmark codifica en HTML los valores interpolados de forma predeterminada, y ofrece una forma de triple llave que desactiva dicha codificación.
No recurras a las triples llaves en un campo que haya rellenado un desconocido: la documentación de Postmark advierte que no escapar el contenido crea un riesgo siempre que la salida se renderice en un navegador, algo que ocurre frecuentemente con los correos de notificación.
Termina este paso con una plantilla que puedas enviar a modo de prueba desde el interior de Postmark.
2. Coloca el token del servidor en tu entorno de Webflow Cloud
El token es una credencial de nivel de servidor que puede enviar correos en tu nombre, por lo que debe estar en el entorno y no en el repositorio.
Abre el panel de Despliegues (Deployments) del entorno, haz clic en Variables de entorno y añade los tres valores que necesita el controlador:
POSTMARK_SERVER_TOKEN=your-server-token
POSTMARK_FROM=notifications@yourdomain.com
POSTMARK_TO=team@yourdomain.com
Marca el token como Secreto para cifrarlo y ocultarlo en el panel. Webflow Cloud pone las variables de entorno a disposición de tu compilación y de la aplicación desplegada en tiempo de ejecución, y redacta los valores secretos de los registros de compilación, por lo que un secreto marcado correctamente no aparecerá en el registro de despliegue.
Ninguno de estos lleva un prefijo NEXT_PUBLIC_. Ese prefijo inserta un valor en el paquete del navegador, y un token de servidor de Postmark en un paquete público permitiría que cualquiera envíe correos desde tu dominio.
Luego, envía un commit, porque añadir una variable no la aplica retroactivamente al despliegue en ejecución. Si te saltas esto, el token quedará indefinido en producción, lo que se mostrará como un error 401 de Postmark en lugar de cualquier mensaje relacionado con la configuración.
Terminarás este paso con tres variables configuradas, un despliegue que puede verlas y ninguna de ellas legible desde el código fuente de la página.
3. Envía con el endpoint de plantillas
Las plantillas utilizan un endpoint diferente al de los envíos simples. En lugar de /email con un asunto y un cuerpo, haces un post a /email/withTemplate con una referencia de plantilla y un modelo.
Añade esto junto a cualquier asistente de envío existente en lugar de reemplazarlo, ya que la ruta /email sigue siendo la opción correcta para mensajes que no tienen una plantilla detrás:
// lib/postmark.ts
type SendTemplateArgs = {
templateAlias: string
to: string
model: Record<string, unknown>
replyTo?: string
}
export async function sendTemplate({
templateAlias,
to,
model,
replyTo,
}: SendTemplateArgs) {
const res = await fetch('https://api.postmarkapp.com/email/withTemplate', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
// Server-level token, from the API Tokens tab of your Postmark server.
'X-Postmark-Server-Token': process.env.POSTMARK_SERVER_TOKEN!,
},
body: JSON.stringify({
From: process.env.POSTMARK_FROM!,
To: to,
ReplyTo: replyTo,
TemplateAlias: templateAlias,
TemplateModel: model,
}),
})
// Only 422 and 503 are documented as carrying a JSON body, so
// parsing before checking the status turns a 429 or a 500 into an
// unrelated SyntaxError in your logs.
if (!res.ok) {
const detail = await res.text()
throw new Error(`Postmark HTTP ${res.status}: ${detail.slice(0, 200)}`)
}
const body = await res.json()
// A 200 can still carry a non-zero ErrorCode, so read it rather
// than trusting the status alone.
if (body.ErrorCode !== 0) {
throw new Error(`Postmark ${body.ErrorCode}: ${body.Message}`)
}
return body.MessageID as string
}
Hay dos detalles ahí que vale la pena entender en lugar de solo copiar. TemplateModel es el objeto que Postmark utiliza para generar el asunto, el cuerpo HTML y el cuerpo de texto, por lo que es el único lugar donde tus datos entran en el correo electrónico.
Y la comprobación de errores lee ErrorCode en lugar de confiar en el estado HTTP, porque Postmark responde a la mayoría de los problemas de entrada con un 422 y un código numérico en el cuerpo, y repite ese código en el encabezado de respuesta X-PM-ApiErrorCode.
Una ventaja que obtienes gratis: si tu plantilla tiene un cuerpo HTML con un bloque de estilo, Postmark lo integra automáticamente en el HTML renderizado, lo que ayuda a que el correo sobreviva al contacto con los clientes de correo. Puedes desactivarlo con InlineCss: false, aunque rara vez hay motivos para hacerlo.
Observa lo que falta deliberadamente: no hay MessageStream. Omitirlo hace que el mensaje se envíe por defecto al flujo transaccional de salida, que es el flujo correcto para una notificación de formulario.
Configúralo solo si has cambiado los flujos de tu servidor y evita dirigir las notificaciones de formulario a un flujo de difusión, ya que estos conllevan un requisito de cancelación de suscripción que no tiene sentido para una alerta interna.
Terminas este paso con un asistente que devuelve un ID de mensaje o lanza algo que puedes leer.
4. Dirige cada formulario a su propia plantilla
Un solo controlador puede gestionar todos los formularios del sitio, siempre que sepa a qué plantilla corresponde cada formulario y qué campos espera esa plantilla.
La versión a continuación utiliza un segmento dinámico para que /api/forms/contact y /api/forms/demo lleguen aquí:
// app/api/forms/[form]/route.ts
import { NextResponse, type NextRequest } from 'next/server'
import { sendTemplate } from '@/lib/postmark'
// One place that decides which template a form uses, and which
// fields that template is allowed to see.
const FORMS = {
contact: {
templateAlias: 'contact-enquiry',
fields: ['name', 'email', 'message'] as const,
},
demo: {
templateAlias: 'demo-request',
fields: ['name', 'email', 'company', 'team_size'] as const,
},
} as const
export async function POST(
request: NextRequest,
{ params }: { params: Promise<{ form: string }> },
) {
const { form } = await params
const config = FORMS[form as keyof typeof FORMS]
if (!config) {
return NextResponse.json({ error: 'Unknown form' }, { status: 404 })
}
const submitted = (await request.json()) as Record<string, string>
// Build the model from the allow-list, and FAIL on a field the
// form did not send. Defaulting a missing field to '' is what turns
// a renamed Designer field into an email with a silent gap in it.
const model: Record<string, string> = {}
const missing: string[] = []
for (const field of config.fields) {
const value = submitted[field]
if (typeof value !== 'string' || value.trim() === '') {
missing.push(field)
continue
}
model[field] = value.trim()
}
if (missing.length > 0) {
// Loud in your logs, not blank in somebody's inbox.
console.error('Form/template mismatch', { form, missing })
return NextResponse.json(
{ error: 'Missing fields', missing },
{ status: 422 },
)
}
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(model.email)) {
// ReplyTo is the only address a submitter controls, and an
// invalid one fails the whole send with ErrorCode 300.
return NextResponse.json({ error: 'Invalid email' }, { status: 422 })
}
try {
const messageId = await sendTemplate({
templateAlias: config.templateAlias,
to: process.env.POSTMARK_TO!,
replyTo: model.email,
model,
})
return NextResponse.json({ ok: true, messageId })
} catch (error) {
console.error('Postmark send failed', error)
return NextResponse.json({ error: 'Could not send' }, { status: 502 })
}
}
La lista de permitidos junto con la verificación de campos faltantes evita toda una categoría de errores silenciosos, y solo funciona gracias a esa comprobación. El sistema de plantillas de Postmark es deliberadamente permisivo: omite cualquier referencia a un valor nulo, falso o vacío, por lo que un marcador de posición sin una clave de modelo coincidente no muestra nada y el envío sigue devolviendo un éxito.
Cambia el nombre de un campo en el Diseñador y el correo llegará con un hueco que nadie nota hasta que un colega lo menciona.
Observa lo que sucedería con la versión más corta y obvia. Escribir model[field] = (submitted[field] ?? '').trim() parece hacer el mismo trabajo, pero convierte el campo faltante en una cadena vacía y se la entrega a Postmark, que es exactamente el resultado que la lista de permitidos debía evitar.
Fallar ante la ausencia es lo que convierte un defecto invisible en el correo en un error 422 en tus propios registros. Ten en cuenta que params se espera (await). En Next.js 15, que es lo que ejecuta Webflow Cloud, los parámetros de ruta dinámica llegan como una promesa.
Terminas este paso con un endpoint que sirve a todos los formularios y un objeto que describe lo que envía cada uno.
5. Conecta el formulario de Webflow al controlador
La acción de formulario nativa de Webflow envía los datos a Webflow. Para llegar a tu controlador de ruta, intercepta el envío y publica los campos tú mismo.
Primero, dale al formulario el atributo que el script selecciona. En el elemento del formulario dentro del Diseñador, añade data-pm-form="contact", usando el mismo slug que la clave en tu mapa FORMS , ya que ese atributo es lo que le indica al script a qué endpoint enviar los datos.
Luego añade esto en un bloque de código (Code Embed), colocado después del formulario en la página para que el elemento exista cuando se ejecute el script:
<!-- A Code Embed on the page holding your Webflow form -->
<script>
document.querySelectorAll('[data-pm-form]').forEach(function (form) {
form.addEventListener('submit', async function (event) {
event.preventDefault()
var slug = form.dataset.pmForm
var payload = Object.fromEntries(new FormData(form).entries())
// The mount path matters: a Webflow Cloud app is served under
// it, and a bare /api/... hits the parent Webflow site instead.
var button = form.querySelector('[type="submit"]')
if (button) button.disabled = true
try {
var res = await fetch('/app/api/forms/' + slug, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
form.setAttribute('data-pm-state', res.ok ? 'sent' : 'error')
} catch (err) {
// A rejected fetch writes neither state, so the visitor sees
// nothing at all unless you catch it.
form.setAttribute('data-pm-state', 'error')
} finally {
if (button) button.disabled = false
}
})
})
</script>
La ruta de montaje es donde la gente suele tropezar. Una aplicación de Webflow Cloud se sirve bajo la ruta de montaje que elegiste para el entorno, por lo que una llamada del lado del cliente a /api/forms/contact llega a tu sitio principal de Webflow y devuelve su página 404 en lugar de tu controlador.
Las peticiones (fetches) del lado del cliente deben llevar ese prefijo; las definiciones de ruta del servidor no, porque Next.js lo aplica por ellas.
Como el formulario se envía a través de tu controlador, mantén cualquier control de spam en el que ya confíes. La protección de formularios propia de Webflow reside en el endpoint de Webflow, no en el tuyo.
Terminas este paso con un formulario que genera un correo con plantilla y un atributo de estado que puedes usar para dar estilo a un mensaje de éxito.
¿Por qué fallan los envíos de plantillas de Postmark desde Webflow?
Los envíos de plantillas fallan de forma distinta a los envíos simples: por lo general, la solicitud está bien formada, por lo que lo que recibes es un código numérico sobre la plantilla en lugar de un error de transporte.
Estos cuatro cubren casi todo, y el segundo no produce ningún error.
Cada envío devuelve el ErrorCode 1101
Causa: Postmark documenta el 1101 como una solicitud que no especifica ni ni o que no encuentra la plantilla, el alias o el diseño referenciados. En la práctica, en una implementación como esta, casi siempre se trata de un error de coincidencia de alias: la cadena en tu mapa no coincide con el alias guardado en la plantilla.TemplateIdTemplateAliasFORMS
Solución: Compara ambos caracteres por carácter y verifica qué servidor de Postmark estás consultando. Las plantillas pertenecen a un servidor, y un token de un servidor diferente en la misma cuenta no las encontrará, lo que produce el mismo 1101 y hace que la gente busque un error tipográfico que no existe.
El código también cubre un diseño faltante, por lo que una plantilla que hace referencia a un alias de diseño que ha sido renombrado falla de esta manera, incluso cuando el alias de la propia plantilla es correcto.
El correo electrónico llega con espacios vacíos donde deberían estar los datos
Causa: Las claves en tu no coinciden con los marcadores de posición en la plantilla. Este es el fallo contra el que vale la pena diseñar, porque nada lo reporta: la solicitud es válida, Postmark devuelve un ID de mensaje, tu gestor registra un éxito y el único síntoma es un correo electrónico con un espacio donde debería ir un nombre.TemplateModel
Solución: Lee los marcadores de posición de la plantilla y tu lista de campos uno al lado del otro, recordando que la asignación debe mantenerse en tres lugares: el nombre del campo del formulario de Webflow, la clave que pusiste en el modelo y el marcador de posición en la plantilla.
La lista de permitidos en el paso 4 es la solución estructural, ya que coloca los nombres de los campos en un lugar revisable en lugar de dejarlos implícitos en lo que sea que el formulario haya enviado. Envía una prueba desde el propio editor de Postmark con un modelo de muestra para confirmar que la plantilla se renderiza antes de culpar al gestor.
ErrorCode 300 en un envío que parece completo
Causa: El 300 de Postmark es un error de validación de envío que cubre una variedad de problemas: cero destinatarios, una dirección no válida, un cuerpo de texto o HTML faltante y varios límites de destinatarios, metadatos, archivos adjuntos y encabezados. Desde un gestor de formularios, los culpables habituales son un vacío porque una variable de entorno no se resolvió, o una dirección mal formada que llegó a través del formulario.To
Solución: Lee el campo junto con el código, ya que un código de error cubre varios mensajes y el texto es lo que te indica cuál es. Confirma que y estén realmente configurados en el entorno en el que realizaste la implementación y no solo en , que es solo local.MessagePOSTMARK_FROMPOSTMARK_TO.env.local
Luego mira , porque en esta compilación es la única dirección que controla quien envía y, por lo tanto, la única que puede estar mal formada. El gestor anterior la valida antes de enviarla por esa razón: una dirección de Responder-A no validada no falla silenciosamente; hace que falle toda la notificación.ReplyTo
No se puede crear una nueva plantilla o aparece el ErrorCode 1105
Causa: Has alcanzado el límite de plantillas del servidor. Postmark permite hasta 100 plantillas por servidor y documenta el 1105 como una solicitud que excedería el límite de plantillas activas. Esto suele aparecer en cuentas que generan una plantilla por campaña o por cliente en lugar de por tipo de mensaje.
Solución: Consolida. La mayor parte de la proliferación de plantillas es un problema de modelo, no de contenido: tres plantillas que solo se diferencian en un encabezado son una sola plantilla con un encabezado en el modelo. Mueve el texto variable a y elimina los duplicados.TemplateModel
Si realmente necesitas más de 100, la documentación de Postmark dice que contactes al soporte, y separar las responsabilidades entre servidores vale la pena de todos modos, ya que tanto las plantillas como los tokens tienen un alcance de servidor.
Qué construir a continuación con formularios de Webflow y Postmark
Si empiezas desde cero en lugar de migrar, nuestra guía sobre correo electrónico transaccional en Webflow Cloud cubre la verificación del remitente y la ruta de envío simple sobre la que se basa esta, y la guía de SendGrid cubre la misma tarea con otro proveedor, incluyendo validación del lado del servidor y gestión de spam que vale la pena aprovechar en cualquier caso.
Para los detalles de conexión, consulta la integración de Webflow y Postmark.
Preguntas frecuentes
¿Debo enviar mediante TemplateAlias o TemplateId?
Cualquiera funciona, pero necesitas uno. Prefiere el alias: es un nombre que tú eliges, se lee claramente en el código y sobrevive si la plantilla se vuelve a crear. Postmark asigna un ID numérico que no te dice nada en el punto de llamada.
¿Puede un solo controlador gestionar todos los formularios del sitio?
Sí, y es la opción más ordenada. Usa un segmento de ruta dinámico para el nombre del formulario y mantén un mapa que relacione el formulario con el alias de la plantilla y los campos que esta espera; así, añadir un formulario será solo un cambio de configuración y no un nuevo endpoint.
¿Por qué mi correo se envía correctamente pero los campos aparecen vacíos?
Tus claves de modelo no coinciden con los marcadores de posición de la plantilla. No hay errores porque la solicitud es válida, así que el único síntoma es el hueco en el correo. Comprueba que el nombre del campo, la clave del modelo y el marcador coincidan.
¿Sigo necesitando la protección contra spam de Webflow?
La protección de formularios de Webflow se aplica a su propio endpoint, y esta implementación envía los datos al tuyo. Añade tus propias comprobaciones del lado del servidor en el controlador de ruta en lugar de asumir que la plataforma te sigue cubriendo.
¿Cuántas plantillas puedo tener?
Postmark permite hasta 100 por servidor y no procesará solicitudes que superen ese límite. Si te acercas a él, la solución habitual es mover el texto variable al modelo de la plantilla en lugar de crear plantillas casi duplicadas.





