# Añade un formulario de contacto a un sitio web estático en Amazon S3

> Los website endpoints de S3 solo aceptan GET y HEAD por HTTP plano, así que envía el formulario de contacto a un backend y configura bien las redirecciones con CloudFront.

Publicado el 2026-10-25 · https://ikiform.com/es/guides/contact-form-static-site/aws-s3

Un website endpoint de S3 responde al `POST` de un formulario con `405 MethodNotAllowed`, así que apunta el `action` del formulario a tu endpoint de Ikiform. Como el endpoint es `http:` plano, un `_redirect` de vuelta a él nunca se sigue; configura **Redirect after submit** en los ajustes del formulario, o pon CloudFront delante para tener `https:`. Con CloudFront delante del endpoint REST del bucket, redirige a un nombre de archivo completo como `thanks.html`, porque el default root object solo se aplica a la raíz.

## Qué cambia en S3

Un bucket configurado para alojar un sitio web estático tiene su propio website endpoint, como `http://www.example.com.s3-website-us-east-1.amazonaws.com`. AWS documenta que solo admite peticiones `GET` y `HEAD` sobre objetos. El envío de un formulario recibe una página de error HTML:

`Respuesta a un POST`:

```html
<html>
<head><title>405 Method Not Allowed</title></head>
<body>
<h1>405 Method Not Allowed</h1>
<ul>
<li>Code: MethodNotAllowed</li>
<li>Message: The specified method is not allowed against this resource.</li>
<li>Method: POST</li>
<li>ResourceType: OBJECT</li>
```

Otros tres rasgos de S3 condicionan el formulario de contacto:

| Rasgo de S3 | Qué significa para el formulario |
| --- | --- |
| Los website endpoints no admiten HTTPS | Ikiform solo sigue un `_redirect` a una URL `https:`, así que se ignora una redirección de vuelta al endpoint sin más |
| Las cabeceras de respuesta vienen de CloudFront, no del bucket | La Content-Security-Policy se define en una política de cabeceras de respuesta de CloudFront |
| El default root object de CloudFront solo se aplica a la raíz | Con un origen REST, `https://example.com/thanks/` no carga `thanks/index.html`; redirige a `thanks.html` |

Para un sitio `https:`, AWS recomienda Amplify Hosting, que funciona sobre CloudFront, o CloudFront delante del bucket. El formulario funciona en ambos casos. Lo que cambia es dónde acaba el visitante después de enviar.

Diagrama: Dónde acaba el visitante después de enviar, con y sin CloudFront

1. En el website endpoint sin más, la página se sirve por http y envía a Ikiform.
2. Ikiform guarda el mensaje, pero ignora un _redirect a una dirección http y muestra su página de agradecimiento alojada.
3. Configurar Redirect after submit en el panel es la forma de evitarlo, porque acepta URL http.
4. Detrás de CloudFront con tu propio dominio, la página es https.
5. Ikiform sigue el _redirect a tu thanks.html, siempre que el dominio esté en Allowed domains.

El mensaje se guarda en ambos casos. Solo el último paso, a dónde va el navegador, depende de https.

## Qué origen de CloudFront usar

CloudFront puede leer del bucket de dos formas, y la elección decide cómo debe ser tu URL de agradecimiento:

| Origen | Bucket | Páginas índice de carpetas | URL de agradecimiento |
| --- | --- | --- | --- |
| El website endpoint de S3, como origen personalizado | Debe ser de lectura pública | Funcionan: `/thanks/` carga `thanks/index.html` | `/thanks/` o `/thanks.html` |
| El endpoint REST, con origin access control | Sigue siendo privado, solo lo lee CloudFront | Solo la raíz, mediante el default root object | `/thanks.html`, un nombre de archivo completo |

Un nombre de archivo completo funciona con ambos, y por eso los ejemplos lo usan. Si prefieres no gestionar CloudFront tú, Amplify Hosting puede desplegar desde el bucket con `https:` y una CDN gestionada; ahí el formulario tampoco necesita cambios.

## El formulario

Los sitios en S3 suelen ser sitios de empresa, así que este formulario pide un email de trabajo y el nombre de la empresa. Súbelo como `contact.html` junto a `thanks.html`:

`contact.html`:

```html
<form action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
  <label for="name">Name</label>
  <input id="name" name="name" autocomplete="name" required />

  <label for="email">Work email</label>
  <input id="email" name="email" type="email" autocomplete="email" required />

  <label for="company">Company</label>
  <input id="company" name="company" autocomplete="organization" />

  <label for="message">How can we help?</label>
  <textarea id="message" name="message" rows="6" minlength="10" required></textarea>

  <input type="hidden" name="_redirect" value="https://www.example.com/thanks.html" />
  <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
  <button type="submit">Send</button>
</form>
```

- El `_redirect` apunta al dominio de CloudFront, con un nombre de archivo. Solo se sigue cuando `www.example.com` o `example.com` está en **Allowed domains**.
- En el endpoint `http:` sin más, quita el input `_redirect` y configura **Redirect after submit** con la página de agradecimiento. Ese ajuste acepta URL `http:`, porque solo tu equipo puede cambiarlo.
- Mantén `email` como nombre del campo. Los emails de notificación lo usan como dirección de respuesta.

En un sitio de empresa, normalmente más de una persona lee la bandeja de contacto. Añade hasta 10 destinatarios de notificaciones confirmados al formulario; cada uno recibe el mensaje, y quien pulse **Reply** responde directamente al remitente. Si los mensajes también deben llegar a un canal compartido o a una cola de tickets, añade un webhook firmado y pásalo por [Zapier](https://ikiform.com/blog/form-submissions-to-zapier.md) o por tu propio código. El campo `company` llega como texto plano, listo para filtrar en cualquiera de los dos sitios.

Antes de subirlo, prueba la página desde un servidor local con `localhost` en **Allowed domains**. Los envíos desde `localhost` cuentan como pruebas, así que puedes dejar el formulario bien antes de que el bucket llegue a servirlo.

Sincroniza la carpeta con el bucket e invalida las páginas en CloudFront para que el nuevo formulario se sirva de inmediato:

`Terminal`:

```bash
aws s3 sync ./site s3://www.example.com --delete

aws cloudfront create-invalidation --distribution-id E1EXAMPLE --paths "/contact.html" "/thanks.html"
```

## Dominios permitidos en hosts de AWS

| Dónde se sirve la página | Añade |
| --- | --- |
| Website endpoint sin más | El host completo del endpoint del bucket, como `www.example.com.s3-website-us-east-1.amazonaws.com` |
| Dominio por defecto de CloudFront | El host exacto `d111111abcdef8.cloudfront.net`, mientras pruebas |
| Tu propio dominio | `example.com`, que también cubre `www.example.com` |

Nunca añadas `amazonaws.com`, un sufijo regional `s3-website` o `cloudfront.net` por sí solos. Coincidiría cualquier bucket o distribución del mundo. AWS registra los dominios de los website endpoints de S3 en la Public Suffix List por la misma razón: cada bucket es un sitio distinto.

## Cabeceras a través de CloudFront

Los metadatos de objeto de S3 no pueden definir una Content-Security-Policy, así que defínela en una política de cabeceras de respuesta de CloudFront y asóciala al comportamiento de caché por defecto de la distribución. El valor de la cabecera está limitado a 1.783 caracteres:

`csp-policy.json`:

```json
{
  "Name": "site-csp",
  "SecurityHeadersConfig": {
    "ContentSecurityPolicy": {
      "Override": true,
      "ContentSecurityPolicy": "default-src 'self'; form-action 'self' https://ikiform.com; connect-src 'self' https://ikiform.com"
    }
  }
}
```

`Terminal`:

```bash
aws cloudfront create-response-headers-policy --response-headers-policy-config file://csp-policy.json
```

> `Override: true` sustituye cualquier CSP que envíe el origen. Con un origen S3 sin más no hay ninguna, pero así la política sigue mandando si cambias de origen más adelante.

## Comprueba que funciona

1. Añade los dominios de la tabla de arriba, más `localhost` mientras pruebas.
2. Envía desde tu dominio de CloudFront. Deberías llegar a `thanks.html` en tu propio dominio.
3. Si también mantienes público el website endpoint sin más, envía desde él y confirma que llegas a la página configurada en **Redirect after submit**.
4. Abre **Submissions** en el panel. El nuevo envío está ahí con todos los campos que enviaste.
5. Revisa la bandeja de entrada de cada destinatario de notificaciones. Cada uno recibe un email con los campos, y **Reply** va a quien envió el formulario cuando este tiene un campo `email`.
6. ¿No llegó nada? Cambia el filtro de estado del panel a **Spam**. El spam recibe la misma respuesta de éxito que un envío real.

## Problemas habituales

| Problema | Qué hacer |
| --- | --- |
| `405 Method Not Allowed` con `Code: MethodNotAllowed` | El formulario envió al bucket. Puede que CloudFront aún sirva el `contact.html` antiguo; invalídalo. |
| `AccessDenied` o `403` después de la redirección | La redirección apunta a una carpeta como `/thanks/` en una distribución de CloudFront con origen REST. Apúntala a `thanks.html`. |
| El navegador llega a la página de agradecimiento de Ikiform en lugar de a la tuya | `_redirect` se ignora salvo que su dominio esté en **Allowed domains** y sea una URL `https:`. Añade el dominio, o configura **Redirect after submit** en los ajustes del formulario. |
| `403 origin_not_allowed`, o todos los envíos acaban en spam | El dominio de la página no está en **Allowed domains** del formulario. Añádelo, más `localhost` para las pruebas locales. Los subdominios de un dominio de la lista están permitidos. |

## Preguntas

### ¿Puede un sitio web estático en S3 gestionar envíos de formularios?

No. Los website endpoints de S3 solo admiten peticiones GET y HEAD, así que un POST recibe 405 MethodNotAllowed. Apunta el formulario a un backend de formularios, o construye una API con Lambda.

### ¿Necesito CloudFront para que funcione el formulario de contacto?

No. El formulario también envía a Ikiform desde una página http. CloudFront te da https, lo que permite a Ikiform seguir un _redirect de vuelta a tu propia página de agradecimiento.

### ¿Por qué no API Gateway, Lambda y SES?

Funciona y lo mantiene todo en AWS, pero tienes que construir tú la validación, el filtrado de spam, el almacenamiento y los reintentos, y SES necesita un remitente verificado. Un backend de formularios da menos mantenimiento para un formulario de contacto.

### ¿CloudFront guardará en caché el formulario antiguo después de cambiarlo?

Puede, hasta que caduque la copia en caché. Invalida contact.html después de cada cambio, o sube el HTML con un max-age de Cache-Control corto para que los formularios nuevos se publiquen rápido.

### ¿Ikiform es gratis?

No, no hay plan gratuito. Probar es gratis: puedes enviar 50 envíos de prueba al mes desde localhost antes de pagar. Para pasar a producción, Pro empieza en 6 $/mes por 500 envíos, o puedes pagar una sola vez 10.000 envíos por 49 $ o 50.000 envíos por 149, $ con créditos que no caducan nunca.

## Más guías

- [Formulario de contacto en un sitio estático](https://ikiform.com/es/guides/contact-form-static-site.md): Añade un formulario de contacto que funcione a un sitio estático sin servidor: a dónde va el POST, el spam, las respuestas por email y código para cada host y framework.
- [Cloudflare Pages](https://ikiform.com/es/guides/contact-form-static-site/cloudflare-pages.md): Añade un formulario de contacto a Cloudflare Pages sin escribir una Function: envía a un backend de formularios, define cabeceras en _headers y usa tus propias claves de Turnstile.
- [GitHub Pages](https://ikiform.com/es/guides/contact-form-static-site/github-pages.md): GitHub Pages no ejecuta código de servidor, así que un POST de formulario recibe 405 Not Allowed; envía a un backend de formularios y configura bien las redirecciones del project site.
- [Vercel](https://ikiform.com/es/guides/contact-form-static-site/vercel.md): Una página estática en Vercel responde al POST de un formulario con 405 Method Not Allowed; aquí verás por qué y cómo enviar el formulario a un backend.
- [Netlify](https://ikiform.com/es/guides/contact-form-static-site/netlify.md): Usa un formulario de contacto en un sitio de Netlify sin Netlify Forms: sin detección en el build, envíos JSON desde React y un formulario aparte para las deploy previews.
- [Web de portfolio](https://ikiform.com/es/guides/contact-form-static-site/portfolio-website.md): Un formulario de contacto para portfolio que hace las preguntas de una primera llamada: proyecto, presupuesto, plazos y un enlace, sin servidor y respondiendo desde tu bandeja.
