# Menambahkan formulir kontak ke website statis Amazon S3

> Endpoint website S3 hanya menerima GET dan HEAD lewat HTTP biasa, jadi kirim formulir kontak ke form backend dan atur redirect dengan benar lewat CloudFront.

Terbit 2026-10-25 · https://ikiform.com/id/guides/contact-form-static-site/aws-s3

Endpoint website S3 menjawab `POST` formulir dengan `405 MethodNotAllowed`, jadi arahkan `action` formulir ke endpoint Ikiform Anda. Karena endpoint-nya `http:` biasa, `_redirect` kembali ke sana tidak pernah diikuti; atur **Redirect after submit** di pengaturan formulir, atau pasang CloudFront di depannya untuk `https:`. Dengan CloudFront di depan REST endpoint bucket, redirect ke nama file lengkap seperti `thanks.html`, karena default root object hanya berlaku untuk root.

## Apa yang berbeda di S3

Bucket yang dikonfigurasi untuk static website hosting mendapat website endpoint sendiri, seperti `http://www.example.com.s3-website-us-east-1.amazonaws.com`. AWS mendokumentasikan bahwa endpoint ini hanya mendukung request `GET` dan `HEAD` pada objek. Kiriman formulir mendapat halaman error HTML:

`Respons untuk 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>
```

Tiga ciri S3 lainnya memengaruhi formulir kontak:

| Ciri S3 | Artinya bagi formulir |
| --- | --- |
| Website endpoint tidak mendukung HTTPS | Ikiform hanya mengikuti `_redirect` ke URL `https:`, jadi redirect kembali ke endpoint polos diabaikan |
| Header respons berasal dari CloudFront, bukan bucket | Content-Security-Policy diatur di response headers policy CloudFront |
| Default root object CloudFront hanya berlaku untuk root | Dengan REST origin, `https://example.com/thanks/` tidak memuat `thanks/index.html`; redirect ke `thanks.html` |

Untuk situs `https:`, AWS merekomendasikan Amplify Hosting, yang berjalan di CloudFront, atau CloudFront di depan bucket. Formulir berfungsi dengan keduanya. Yang berubah adalah ke mana pengunjung berakhir setelah mengirim.

Diagram: Ke mana pengunjung mendarat setelah mengirim, dengan dan tanpa CloudFront

1. Di website endpoint polos, halaman disajikan lewat http dan mengirim ke Ikiform.
2. Ikiform menyimpan pesan, tetapi mengabaikan _redirect ke alamat http dan menampilkan halaman terima kasih hosted.
3. Mengatur Redirect after submit di dashboard adalah jalan keluarnya, karena pengaturan itu menerima URL http.
4. Di balik CloudFront dengan domain Anda sendiri, halaman memakai https.
5. Ikiform mengikuti _redirect ke thanks.html Anda, selama domainnya ada di Allowed domains.

Pesan tetap disimpan bagaimanapun juga. Hanya langkah terakhir, ke mana browser pergi, yang bergantung pada https.

## CloudFront origin yang mana

CloudFront bisa membaca dari bucket dengan dua cara, dan pilihannya menentukan bentuk URL terima kasih Anda:

| Origin | Bucket | Halaman index folder | URL terima kasih |
| --- | --- | --- | --- |
| Website endpoint S3, sebagai custom origin | Harus bisa dibaca publik | Berfungsi: `/thanks/` memuat `thanks/index.html` | `/thanks/` atau `/thanks.html` |
| REST endpoint, dengan origin access control | Tetap privat, hanya dibaca oleh CloudFront | Hanya root, lewat default root object | `/thanks.html`, nama file lengkap |

Nama file lengkap berfungsi dengan keduanya, itulah sebabnya contoh-contohnya memakai itu. Jika Anda tidak ingin mengelola CloudFront sendiri, Amplify Hosting bisa deploy dari bucket dengan `https:` dan CDN terkelola; formulir juga tidak perlu diubah di sana.

## Formulirnya

Situs S3 sering kali situs perusahaan, jadi formulir ini meminta email kantor dan nama perusahaan. Upload sebagai `contact.html` di samping `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>
```

- `_redirect` mengarah ke domain CloudFront, dengan nama file. Redirect hanya diikuti jika `www.example.com` atau `example.com` ada di **Allowed domains**.
- Di endpoint `http:` polos, hapus input `_redirect` dan atur **Redirect after submit** ke halaman terima kasih sebagai gantinya. Pengaturan itu menerima URL `http:`, karena hanya tim Anda yang bisa mengubahnya.
- Pertahankan `email` sebagai nama field. Email notifikasi memakainya sebagai alamat reply-to.

Di situs perusahaan, biasanya lebih dari satu orang membaca inbox kontak. Tambahkan hingga 10 penerima notifikasi terkonfirmasi ke formulir; masing-masing menerima pesan, dan siapa pun yang menekan **Reply** langsung menjawab pengirim. Jika pesan juga perlu sampai ke channel bersama atau antrean tiket, tambahkan webhook bertanda tangan dan arahkan lewat [Zapier](https://ikiform.com/blog/form-submissions-to-zapier.md) atau kode Anda sendiri. Field `company` datang sebagai teks biasa, jadi siap difilter di kedua tempat.

Sebelum upload, uji halaman dari server lokal dengan `localhost` di **Allowed domains**. Kiriman dari `localhost` dihitung sebagai uji, jadi Anda bisa menyempurnakan formulir sebelum bucket menyajikannya.

Sinkronkan folder ke bucket, lalu invalidate halaman di CloudFront agar formulir baru langsung disajikan:

`Terminal`:

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

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

## Domain yang diizinkan di host AWS

| Tempat halaman disajikan | Tambahkan |
| --- | --- |
| Website endpoint polos | Host endpoint lengkap bucket, seperti `www.example.com.s3-website-us-east-1.amazonaws.com` |
| Domain default CloudFront | Host `d111111abcdef8.cloudfront.net` persisnya, selama pengujian |
| Domain Anda sendiri | `example.com`, yang juga mencakup `www.example.com` |

Jangan pernah menambahkan `amazonaws.com`, sufiks regional `s3-website`, atau `cloudfront.net` begitu saja. Setiap bucket atau distribusi di dunia akan cocok. AWS mendaftarkan domain website endpoint S3 di Public Suffix List untuk alasan yang sama: setiap bucket adalah situs terpisah.

## Header lewat CloudFront

Metadata objek S3 tidak bisa mengatur Content-Security-Policy, jadi atur di response headers policy CloudFront dan pasang ke default cache behavior distribusi. Nilai header dibatasi 1.783 karakter:

`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` menggantikan CSP apa pun yang dikirim origin. Dengan origin S3 polos memang tidak ada, tetapi ini menjaga policy tetap berlaku jika nanti Anda mengganti origin.

## Pastikan berfungsi

1. Tambahkan domain dari tabel di atas, ditambah `localhost` selama pengujian.
2. Kirim dari domain CloudFront Anda. Anda seharusnya mendarat di `thanks.html` di domain Anda sendiri.
3. Jika website endpoint polos juga tetap publik, kirim dari sana dan pastikan Anda sampai di halaman yang diatur di **Redirect after submit**.
4. Buka **Submissions** di dashboard. Kiriman baru ada di sana dengan setiap field yang Anda kirim.
5. Cek inbox setiap penerima notifikasi. Masing-masing mendapat email berisi field, dan **Reply** terkirim ke pengirim formulir jika formulir punya field `email`.
6. Tidak ada yang masuk? Ubah filter status di dashboard ke **Spam**. Spam mendapat jawaban sukses yang sama dengan kiriman asli.

## Hal yang sering menjebak

| Masalah | Yang harus dilakukan |
| --- | --- |
| `405 Method Not Allowed` dengan `Code: MethodNotAllowed` | Formulir mengirim ke bucket. CloudFront mungkin masih menyajikan `contact.html` lama; invalidate file itu. |
| `AccessDenied` atau `403` setelah redirect | Redirect mengarah ke folder seperti `/thanks/` di distribusi CloudFront dengan REST origin. Arahkan ke `thanks.html`. |
| Browser mendarat di halaman terima kasih Ikiform, bukan halaman Anda | `_redirect` diabaikan kecuali domainnya ada di **Allowed domains** dan berupa URL `https:`. Tambahkan domainnya, atau atur **Redirect after submit** di pengaturan formulir sebagai gantinya. |
| `403 origin_not_allowed`, atau setiap kiriman masuk ke spam | Domain halaman tidak ada di **Allowed domains** formulir. Tambahkan, ditambah `localhost` untuk uji lokal. Subdomain dari domain yang terdaftar diizinkan. |

## Pertanyaan

### Bisakah website statis S3 menangani kiriman formulir?

Tidak. Website endpoint S3 hanya mendukung request GET dan HEAD, jadi POST mendapat 405 MethodNotAllowed. Arahkan formulir ke form backend, atau bangun API dengan Lambda.

### Apakah saya butuh CloudFront agar formulir kontak berfungsi?

Tidak. Formulir juga bisa mengirim ke Ikiform dari halaman http. CloudFront memberi Anda https, yang memungkinkan Ikiform mengikuti _redirect kembali ke halaman terima kasih Anda sendiri.

### Mengapa tidak memakai API Gateway, Lambda, dan SES?

Itu bisa dan membuat semuanya tetap di AWS, tetapi Anda membangun validasi, penyaringan spam, penyimpanan, dan percobaan ulang sendiri, dan SES butuh pengirim yang terverifikasi. Form backend lebih sedikit pemeliharaannya untuk formulir kontak.

### Apakah CloudFront akan meng-cache formulir lama setelah saya mengubahnya?

Bisa, sampai salinan cache kedaluwarsa. Invalidate contact.html setelah setiap perubahan, atau upload HTML dengan Cache-Control max-age pendek agar formulir baru cepat live.

### Apakah Ikiform gratis?

Tidak, tidak ada paket gratis. Pengujian gratis: Anda bisa mengirim 50 kiriman uji per bulan dari localhost sebelum membayar. Untuk go live, Pro mulai $6/bln untuk 500 kiriman, atau Anda bisa bayar sekali untuk 10.000 kiriman seharga $49 atau 50.000 kiriman seharga $149, dengan kredit yang tidak pernah kedaluwarsa.

## Panduan lainnya

- [Formulir kontak di situs statis](https://ikiform.com/id/guides/contact-form-static-site.md): Pasang formulir kontak yang berfungsi di situs statis tanpa server: ke mana POST dikirim, spam, balasan lewat email, dan kode untuk setiap host dan framework.
- [Cloudflare Pages](https://ikiform.com/id/guides/contact-form-static-site/cloudflare-pages.md): Tambahkan formulir kontak ke Cloudflare Pages tanpa menulis Function: kirim ke form backend, atur header di _headers, dan pakai key Turnstile Anda sendiri.
- [GitHub Pages](https://ikiform.com/id/guides/contact-form-static-site/github-pages.md): GitHub Pages tidak bisa menjalankan kode server, jadi POST formulir mendapat 405 Not Allowed; kirim ke form backend dan atur redirect project site dengan benar.
- [Vercel](https://ikiform.com/id/guides/contact-form-static-site/vercel.md): Halaman statis di Vercel menjawab POST formulir dengan 405 Method Not Allowed; ini penyebabnya, dan cara mengirim formulir ke backend.
- [Netlify](https://ikiform.com/id/guides/contact-form-static-site/netlify.md): Jalankan formulir kontak di situs Netlify tanpa Netlify Forms: tanpa deteksi saat build, kiriman JSON dari React, dan formulir terpisah untuk deploy preview.
- [Website portofolio](https://ikiform.com/id/guides/contact-form-static-site/portfolio-website.md): Formulir kontak portofolio yang menanyakan hal yang biasa Anda tanyakan di panggilan pertama: proyek, budget, waktu, dan link, tanpa server dan dengan balasan dari inbox Anda.
