Referencia de la API de formularios
Detalles técnicos para conectar formularios a MailContact.
¿Usas un creador de sitios? Empieza por Añade un formulario de contacto para los pasos e instrucciones que puedes copiar.
Copia el enlace del formulario desde los ajustes del proyecto. Tiene este formato, con el ID del formulario al final:
POST https://mailcontact.app/api/forms/your-form-idAcepta tanto FormData (lo que envía un formulario HTML nativo) y application/json. Los envíos entregados llegan a tu bandeja y activan tus canales de notificación. Los mensajes retenidos en Spam esperan revisión sin notificaciones ni consumo de cuota.
Campos
| campo | required | notas |
|---|---|---|
name | opcional | Se muestra como remitente. Si está vacío, utiliza el email. Máximo 200 caracteres. |
email | required | Email del remitente. Se valida: ausente o no válido se rechaza con 400. |
subject | opcional | Máximo 300 caracteres. |
message | required | Cuerpo del mensaje. Obligatorio y máximo 5000 caracteres. |
Un campo de texto obligatorio llamado company_website debe estar presente y vacío. Un valor no vacío marca el envío como bot y se descarta sin avisar (es el campo trampa contra el spam). Todos los demás campos desconocidos se ignoran.
El script inserta el campo en formularios marcados. Si falta, lanza FORM_CHECK_MISSING; si está rellenado, el envío se descarta sin avisar. Dejarlo vacío nunca evita las demás comprobaciones de spam, límites o verificación. Un bot más avanzado puede reconocer la trampa.
El cliente alojado descarta una trampa rellenada antes de pedir configuración o enviar y devuelve { ok: true } para mantener la confirmación normal, como la respuesta antispam del endpoint. La API directa conserva la misma trampa en el servidor.
Recomendado: utiliza el script de MailContact
Añadir data-mailcontact-form al formulario y carga el script una vez por página. Conserva los estilos. Gestiona envío, avisos accesibles, verificación y reintentos; conserva el texto en errores y limpia solo tras éxito confirmado.
El controlador bloquea nuevos envíos durante el envío y diez segundos tras el éxito. Los botones y el teclado siguen bloqueados, los campos vuelven a editarse y la confirmación permanece. Los errores permiten reintentar inmediatamente salvo que el servidor proporcione un Retry-After espera.
<form action="https://mailcontact.app/api/forms/your-form-id" method="POST" data-mailcontact-form>
<label>Email <input type="email" name="email" maxlength="320" required /></label>
<label>Message <textarea name="message" maxlength="5000" required></textarea></label>
<button type="submit">Send</button>
<p data-contact-status role="status" aria-live="polite" aria-atomic="true"></p>
</form>
<script defer src="https://mailcontact.app/forms/v1.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>Un script admite varios formularios, incluso los añadidos después. Inserta la trampa al inicializar cada formulario y comprueba que siga presente y vacía al enviar. Si se elimina o desactiva, bloquea el envío y conserva el borrador. Envía a la action, independientemente de dónde se aloje el script. Utiliza HTTPS (o localhost) para la verificación automática. El generador también incluye un noscript ; para una integración manual sin JavaScript, utiliza el ejemplo HTML de abajo.
Controlas HTML, CSS, etiquetas y botón. Mantén los nombres de campos admitidos; la API ignora los demás. Configura data-mailcontact-success="Thanks, we’ll reply soon." en el formulario para personalizar la confirmación. Para un proceso personalizado, utiliza MailContact.send() abajo.
Idioma de los avisos
Los avisos de envío, verificación, confirmación y errores siguen las preferencias del navegador. Se admiten francés, inglés, español, alemán, italiano y portugués, incluidas variantes regionales como fr-CA y pt-BR. Se utiliza la primera preferencia de idioma compatible del navegador; si no hay ninguna, los avisos se muestran en inglés.
Establece data-mailcontact-language="fr" en un formulario automático para forzar el francés independientemente del navegador y de otros formularios. Los idiomas compatibles tienen prioridad; un valor vacío o no compatible utiliza la detección del navegador. Se lee en cada envío. Las etiquetas, el contenido original del botón y data-mailcontact-success siguen bajo tu control; las confirmaciones personalizadas nunca se traducen automáticamente. Los avisos nativos de validación dependen del navegador.
<form action="https://mailcontact.app/api/forms/your-form-id" method="POST"
data-mailcontact-form data-mailcontact-language="fr">
<!-- Keep your existing fields, button and status element here. -->
</form>Los formularios de cuentas Free muestran un pequeño enlace «Processed by mailcontact.app». Pro y Plus no muestran atribución. El script consulta el plan del propietario, por lo que cambia automáticamente sin sustituir el código. La configuración puede almacenarse en caché hasta un minuto. Aplica estilos al data-mailcontact-branding para adaptarlo a tu sitio.
Si tu sitio configura Content Security Policy, permite el origen del script en script-src y el origen del endpoint del formulario en connect-src y form-action. Esta integración no necesita permiso para JavaScript en línea. El v1 recibe mejoras compatibles; las incompatibles futuras tendrán nueva versión. Puedes escuchar el evento propagado mailcontact:success para añadir tu comportamiento de éxito.
Interfaces totalmente personalizadas
Carga el mismo script y llama a MailContact.send() desde tu controlador. Omite data-mailcontact-form para que el controlador automático no interfiera. Tú controlas los pasos, la carga, errores y confirmación; la función gestiona petición, verificación firmada y reenvío. Nunca cambia el DOM ni borra campos.
const endpoint = "https://mailcontact.app/api/forms/your-form-id";
const form = document.querySelector("#contact-form");
MailContact.prepare(form); // Inject once before the visitor fills the form.
const language = form.getAttribute("data-mailcontact-language") || undefined;
const text = MailContact.getMessages(language);
const config = await MailContact.getConfig({ endpoint, language });
// If config.branding is true, show a visible "Processed by mailcontact.app"
// link to https://mailcontact.app in your own form layout.
async function submit() {
showStatus(text.sending);
try {
await MailContact.send({
endpoint,
data: new FormData(form),
language, // Optional: omit for automatic browser language detection.
onVerification: () => showStatus(text.verification),
brandingElement: document.querySelector("[data-mailcontact-branding]"),
// signal: abortController.signal, // optional cancellation
});
showYourConfirmation();
} catch (error) {
// error.code, error.status and error.retryAfter (seconds or null)
// let you choose your own translated feedback. Keep the draft.
showYourError(error);
}
}MailContact.prepare(form) solo añade el campo oculto; no conecta un controlador ni cambia el diseño. Llámalo una vez por formulario montado antes de interactuar. Nunca recrea un campo eliminado ni borra un valor existente. data también acepta un objeto con name, email, subject, message y el campo obligatorio company_website . El cliente captura los campos para reintentos. Confirma recepción o descarta silenciosamente la trampa rellenada con { ok: true }, y lanza MailContact.FormError para errores. Las comprobaciones ausentes utilizan FORM_CHECK_MISSING, los fallos de red utilizan NETWORK_ERROR, la cancelación o el tiempo de espera agotado utiliza ABORTED. El tiempo de espera es de 60 segundos y se admite como máximo un reintento de verificación.
MailContact.getConfig({ endpoint }) realiza un GET público a la URL del formulario y devuelve solo { branding: true | false }. Las interfaces personalizadas muestran esa atribución cuando es obligatoria y pasan su elemento como brandingElement (o una función que lo devuelva). Cada envío comprueba la configuración del servidor. En Free, si falta un enlace a https://mailcontact.app lanza BRANDING_REQUIRED antes de publicar cualquier mensaje; Pro y Plus no necesitan enlace. Es una comprobación de la integración en el navegador, no una prueba del contenido de una página de terceros en el servidor. No revela la identidad ni la facturación del propietario. React, Vue y otros frameworks pueden llamar a estas funciones una vez cargado el script; la API directa sigue disponible.
El cliente personalizado traduce FormError.message automáticamente; code, status y retryAfter no cambian. Pasa language: "fr" a send() o getConfig() para sustituir la detección del navegador. Un controlador personalizado lee el atributo del formulario como se muestra arriba. MailContact.getMessages(language?) devuelve los avisos del idioma para tu interfaz, incluidos sendingButton, sending, verification, success y wait(seconds). Devuelve un objeto independiente para que tus textos personalizados no alteren otros formularios. Si falla la prueba automática, utiliza VERIFICATION_FAILED. No se utiliza ningún servicio de traducción ni petición adicional de idioma; las respuestas JSON de la API conservan sus códigos e idioma actuales.
Verificación automática de envíos sospechosos
El script gestiona la verificación. Los clientes personalizados pueden enviar _verification: "1" para admitir verificación automática. Una petición sospechosa puede devolver HTTP 403 con code: CHALLENGE_REQUIRED y un challenge . No crea mensaje entregado ni consumo de cuota. Resuelve el desafío y reenvía los campos originales con la solución JSON en _proof. El componente independiente /form-challenge.js expone window.mailcontactSolveChallenge(challenge, signal?), que devuelve esa cadena JSON. Las peticiones personalizadas sospechosas con el campo de comprobación obligatorio pero sin soporte de verificación se retienen en Spam; omitir la verificación nunca permite eludir la comprobación.
La prueba caduca en cinco minutos, se vincula a campos, formulario y cliente exactos y solo vale una vez. Conserva el borrador si falla. Los formularios nativos reciben una página automática de verificación. Los muy sospechosos van a Spam sin alertas ni consumo; el éxito público no revela la clasificación.
Formulario HTML sencillo
Versión sin JavaScript: nombra los campos como arriba y el navegador se encarga:
<form action="https://mailcontact.app/api/forms/your-form-id" method="POST">
<input type="text" name="name" placeholder="Your name" />
<input type="email" name="email" placeholder="[email protected]" required />
<input type="text" name="subject" placeholder="Subject" />
<textarea name="message" placeholder="Your message" required></textarea>
<button type="submit">Send</button>
<input name="company_website" type="text" tabindex="-1" autocomplete="off"
style="position:absolute;left:-10000px" aria-hidden="true" />
</form>Un envío nativo abre la respuesta JSON si tiene éxito o una página HTML de verificación cuando es necesario. Utiliza nuestro script para mantener a los visitantes en la página y mostrar el resultado sin navegar.
JSON con fetch
Enviar application/json con un Content-Type . Mismos campos, misma validación:
const res = await fetch("https://mailcontact.app/api/forms/your-form-id", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: "Ada",
company_website: "",
email: "[email protected]",
subject: "Hello",
message: "Trying out the endpoint.",
}),
});
const data = await res.json();
if (data.ok) {
// show your inline "thanks, message sent" state
}También puedes enviar por POST un FormData desde JavaScript: omite el Content-Type y pasa new FormData(formElement) como cuerpo.
CORS
CORS está abierto: el endpoint responde con Access-Control-Allow-Origin: * y gestiona el OPTIONS , de modo que funciona desde cualquier origen: dominio, localhost, pruebas o GitHub Pages. No hace falta autorizar dominios.
Respuestas y errores
Si tiene éxito, el endpoint devuelve 200 con {"ok": true}. Esto también incluye los mensajes retenidos en Spam y los campos trampa rellenados que se descartan sin avisar; no garantiza la entrega en la bandeja de entrada. Los errores de validación son JSON con la estructura {"ok": false, "code": "...", "error": "..."}. La verificación utiliza {"ok": false, "code": "CHALLENGE_REQUIRED", "challenge": {...}}, o una página HTML para envíos nativos del navegador. El JSON mal formado, los tipos de campo incorrectos, los campos duplicados y los campos demasiado largos devuelven 400:
| estado | error | cuando |
|---|---|---|
403 | Se requiere verificación automática. | CHALLENGE_REQUIRED: resuelve el desafío firmado y vuelve a enviar los campos originales con _proof. Una prueba no válida, caducada o reutilizada recibe un nuevo desafío. El script alojado realiza automáticamente un reintento. |
400 | Falta la comprobación del formulario. | FORM_CHECK_MISSING: el company_website no está presente. El script lo inserta al inicializarse; las integraciones nativas y API deben incluirlo explícitamente. |
400 | Es necesario un email válido. | email falta, no es válido o supera los 320 caracteres |
400 | El mensaje no puede estar vacío. | message falta o está vacío |
404 | Formulario desconocido. | el ID de formulario de la URL no existe |
413 | Contenido demasiado grande. | cuerpo de más de 64 KB |
429 | Demasiadas solicitudes. | más de 20 peticiones por minuto desde una IP o 60 mensajes aceptados por minuto en una bandeja |
429 | Demasiadas solicitudes. | Más de 5 envíos aceptados por minuto desde el mismo email al mismo formulario, incluso desde distintas IP. Un Retry-After indica la espera. |
429 | Este mensaje ya se recibió. | El mismo remitente, asunto y mensaje ya se aceptaron en este formulario durante el último minuto. El código es DUPLICATE_MESSAGE. No se crea ningún mensaje, notificación ni consumo de cuota adicional. |
429 | Cuota mensual agotada. | El código de respuesta es MONTHLY_QUOTA_EXCEEDED, con un Retry-After hasta la próxima renovación mensual de cuota. |
429 | La bandeja no puede aceptar más mensajes para revisión. | SPAM_STORAGE_LIMIT: el proyecto ya tiene 500 mensajes pendientes en Spam. No se recibe ningún mensaje adicional. Utiliza otro medio de contacto; la respuesta incluye Retry-After. |
415 | Tipo de contenido no compatible. | Utiliza JSON, datos URL-encoded o multipart. |
Límites
- El cuerpo se limita a 64 KB.
- Límite: 20 peticiones por minuto e IP, conservado entre reinicios; al superarlo devuelve 429 y Retry-After.
- Límite por remitente: 5 envíos aceptados por minuto, formulario y email. Los idénticos se limitan a uno por minuto; las diferencias de espacios se normalizan.
- Longitudes de campo:
name200,subject300,message5000 caracteres (los valores más largos se rechazan); los emails de más de 320 caracteres se rechazan.
Free incluye 50 formularios enviados al mes en un proyecto. Pro incluye 1000 mensajes entre diez proyectos y Plus, 10 000 entre cincuenta. Los mensajes recibidos y las respuestas consumen la misma cuota, compartida por toda la cuenta; consulta precios.
Relacionado
¿Es tu primera vez? Empieza por Primeros pasos. En Pro y Plus, añade un dirección de email de contacto que llegan a la misma bandeja. El Protección contra spam explica cómo evitar el spam en un endpoint abierto. Si quieres un ejemplo listo para usar, prueba el generador de formularios de contacto o el guías por framework.