Contact form on a static site6 min read
Add a contact form to an Amazon S3 static website
S3 website endpoints accept only GET and HEAD over plain HTTP, so post the contact form to a form backend and get redirects right with CloudFront.
An S3 website endpoint answers a form POST with 405 MethodNotAllowed, so point the form's action at your Ikiform endpoint instead. Because the endpoint is plain http:, a _redirect back to it is never followed; set Redirect after submit in the form's settings, or put CloudFront in front for https:. With CloudFront in front of the bucket's REST endpoint, redirect to a full file name such as thanks.html, because the default root object applies only to the root.
What's different on S3
A bucket configured for static website hosting gets its own website endpoint, such as http://www.example.com.s3-website-us-east-1.amazonaws.com. AWS documents that it supports only GET and HEAD requests on objects. A form post gets an HTML error page:
<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>Three more S3 traits shape the contact form:
| S3 trait | What it means for the form |
|---|---|
| Website endpoints don't support HTTPS | Ikiform only follows a _redirect to an https: URL, so a redirect back to the bare endpoint is ignored |
| Response headers come from CloudFront, not the bucket | A Content-Security-Policy is set in a CloudFront response headers policy |
| CloudFront's default root object only applies to the root | With a REST origin, https://example.com/thanks/ doesn't load thanks/index.html; redirect to thanks.html |
For an https: site, AWS recommends Amplify Hosting, which runs on CloudFront, or CloudFront in front of the bucket. The form works either way. What changes is where the visitor ends up after sending.
- On the bare website endpoint, the page is served over http and posts to Ikiform.
- Ikiform stores the message, but ignores a _redirect to an http address and shows its hosted thank-you page.
- Setting Redirect after submit in the dashboard is the way around that, because it accepts http URLs.
- Behind CloudFront with your own domain, the page is https.
- Ikiform follows the _redirect to your thanks.html, as long as the domain is in Allowed domains.
Which CloudFront origin
CloudFront can read from the bucket in two ways, and the choice decides how your thank-you URL must look:
| Origin | Bucket | Folder index pages | Thank-you URL |
|---|---|---|---|
| The S3 website endpoint, as a custom origin | Must be publicly readable | Work: /thanks/ loads thanks/index.html | /thanks/ or /thanks.html |
| The REST endpoint, with origin access control | Stays private, read only by CloudFront | Only the root, through the default root object | /thanks.html, a full file name |
A full file name works with both, which is why the samples use one. If you'd rather not run CloudFront yourself, Amplify Hosting can deploy from the bucket with https: and a managed CDN; the form needs no change there either.
The form
S3 sites are often company sites, so this form asks for a work email and the company name. Upload it as contact.html next to thanks.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>- The
_redirectpoints at the CloudFront domain, with a file name. It's followed only whenwww.example.comorexample.comis under Allowed domains. - On the bare
http:endpoint, remove the_redirectinput and set Redirect after submit to the thank-you page instead. That setting acceptshttp:URLs, because only your team can change it. - Keep
emailas the field name. Notification emails use it as the reply-to address.
On a company site, more than one person usually reads the contact inbox. Add up to 10 confirmed notification recipients to the form; each gets the message, and whoever presses Reply answers the sender directly. If messages should also reach a shared channel or a ticket queue, add a signed webhook and route it through Zapier or your own code. The company field arrives as plain text, so it's ready to filter on in either place.
Before you upload, test the page from a local server with localhost under Allowed domains. Posts from localhost count as tests, so you can get the form right before the bucket ever serves it.
Sync the folder to the bucket, and invalidate the pages in CloudFront so the new form is served right away:
aws s3 sync ./site s3://www.example.com --delete
aws cloudfront create-invalidation --distribution-id E1EXAMPLE --paths "/contact.html" "/thanks.html"Allowed domains on AWS hosts
| Where the page is served | Add |
|---|---|
| Bare website endpoint | The bucket's full endpoint host, such as www.example.com.s3-website-us-east-1.amazonaws.com |
| CloudFront default domain | The exact d111111abcdef8.cloudfront.net host, while you test |
| Your own domain | example.com, which also covers www.example.com |
Never add amazonaws.com, a regional s3-website suffix or cloudfront.net on their own. Every bucket or distribution in the world would match. AWS registers the S3 website endpoint domains in the Public Suffix List for the same reason: each bucket is a separate site.
Headers through CloudFront
S3 object metadata can't set a Content-Security-Policy, so set it in a CloudFront response headers policy and attach it to the distribution's default cache behavior. The header value is limited to 1,783 characters:
{
"Name": "site-csp",
"SecurityHeadersConfig": {
"ContentSecurityPolicy": {
"Override": true,
"ContentSecurityPolicy": "default-src 'self'; form-action 'self' https://ikiform.com; connect-src 'self' https://ikiform.com"
}
}
}aws cloudfront create-response-headers-policy --response-headers-policy-config file://csp-policy.jsonOverride: true replaces any CSP the origin sends. With a bare S3 origin there isn't one, but it keeps the policy in charge if you change origins later.Check it works
- Add the domains from the table above, plus
localhostwhile you test. - Submit from your CloudFront domain. You should land on
thanks.htmlon your own domain. - If you also keep the bare website endpoint public, submit from it and confirm you reach the page set in Redirect after submit.
- Open Submissions in the dashboard. The new submission is there with every field you sent.
- Check the inbox of each notification recipient. Each gets an email with the fields, and Reply goes to the submitter when the form has an
emailfield. - Nothing arrived? Switch the dashboard's status filter to Spam. Spam gets the same success answer as a real submission.
Gotchas
| Problem | What to do |
|---|---|
405 Method Not Allowed with Code: MethodNotAllowed | The form posted to the bucket. CloudFront may still serve the old contact.html; invalidate it. |
AccessDenied or 403 after the redirect | The redirect points at a folder such as /thanks/ on a CloudFront distribution with a REST origin. Point it at thanks.html. |
| The browser lands on Ikiform's thank-you page instead of yours | _redirect is ignored unless its domain is in Allowed domains and it is an https: URL. Add the domain, or set Redirect after submit in the form's settings instead. |
403 origin_not_allowed, or every post lands in spam | The page's domain isn't in the form's Allowed domains. Add it, plus localhost for local tests. Subdomains of a listed domain are allowed. |
Questions
No. S3 website endpoints support only GET and HEAD requests, so a POST gets 405 MethodNotAllowed. Point the form at a form backend, or build an API with Lambda.
No. The form posts to Ikiform from an http page too. CloudFront gives you https, which lets Ikiform follow a _redirect back to your own thank-you page.
That works and keeps everything in AWS, but you build the validation, spam filtering, storage and retries yourself, and SES needs a verified sender. A form backend is less to maintain for a contact form.
It can, until the cached copy expires. Invalidate contact.html after each change, or upload HTML with a short Cache-Control max-age so new forms go live quickly.
No, there is no free plan. Testing is free: you can send 50 test submissions a month from localhost before you pay. To go live, Pro starts at $6/mo for 500 submissions, or you can pay once for 10,000 submissions for $49 or 50,000 submissions for $149, with credits that never expire.
Stuck on a step, or want a guide for your situation? Email hello@ikiform.com.