静态表单,给只有文件的网站做联系表单
静态表单是只发布文件的网站上的联系框。GitHub Pages、Netlify、Cloudflare Pages、Vercel 可以发布页面,Hugo、Jekyll、Astro、Eleventy 可以构建页面。它们不跑 PHP,也不该保存邮箱密码。这个静态表单后端就是一段代码:浏览器把数据 POST 到 https://getformpost.com/api/submit,留言进入你已经在看的收件箱。访客不必打开邮件程序。
打开一个收件箱
注册免费账户并复制访问密钥。密钥可以公开。它只会投递到你保存的通知邮箱。免费方案每个 UTC 月发送 100 条,一个收件箱。新账户还有七天 Pro 额度,不需要银行卡。之后的 Pro 是每月 12 美元。
粘贴一段代码
复制下面步骤之后的表单。action 是提交地址,method 是 POST。名为 access_key 的隐藏字段带上密钥。已登录时,输入框会填好。否则先粘贴密钥再复制。标签和 CSS 仍然用你自己的。这段标记不含脚本。
从线上地址提交
按你平时的方式部署文件,再从公开网址发一条留言。从磁盘打开的文件无法提交。sent 表示邮箱接受了邮件。logged 表示已保存但邮件还没接通。failed 表示保存成功但发送失败。上线前先做一次。
下面的 HTML 会跟着变。不用改代码。
HTML
<form action="https://getformpost.com/api/submit" method="POST">
<input type="hidden" name="access_key" value="YOUR_ACCESS_KEY">
<label>
姓名
<input type="text" name="name" required>
</label>
<label>
邮箱
<input type="email" name="email" required>
</label>
<label>
留言
<textarea name="message" required></textarea>
</label>
<input type="text" name="botcheck" tabindex="-1" autocomplete="off" style="display:none" aria-hidden="true">
<button type="submit">发送</button>
</form>给静态网站主机用的联系表单
按钮没反应,或者 contact.php 返回 404,是因为文件主机没有服务器。小部件会换掉你的标记。静态表单留下标记,只把 POST 交出去。部署文件即可。我们丢掉明显的机器人,存下文字,再发给通知邮箱。以后从 Netlify 迁到 Cloudflare Pages 也不用改,因为 action 从一开始就不指向主机。

粘贴这段代码
静态表单后端就是步骤下面的 HTML,不是插件,也不是嵌在页面里的账户。把访问密钥填进标着「粘贴你的访问密钥」的框。隐藏字段会跟着变,复制一次即可。能读到页面的人都可以提交。他们不能改收件人,也不能读其他消息。不再使用的页面还在提交时,轮换密钥。不要把邮箱密码和这段代码放在一起。

每个主机上的静态站点联系表单
下面每个主机都能发布文件,也都有一个会挡住表单的习惯。使用绝对 action,不要加让主机代收提交的属性,并从部署后的网址测试。静态表单的接口在这些主机上是同一个地址。
GitHub Pages 联系表单
GitHub Pages 发布一个分支或 docs 目录,自己不接收 POST。把表单放进它提供的 HTML 文件。action 是 https://getformpost.com/api/submit,所以 Pages 看不到这次 POST。如果由 Jekyll 构建,把表单放进布局,避免 Markdown 转义 HTML。GitHub Pages 上的静态表单就是那个已发布的文件。从线上网址提交,不要从磁盘上的文件提交。
Netlify Forms 的替代
带有 netlify 或 data-netlify 属性的表单,提交会存在 Netlify 里。文件搬走之后,那些记录留在原地。去掉这个属性,保留绝对 action。不必加 Function。离开 Netlify 之后静态表单仍然有效,因为提交从一开始就没有发往那里。
Cloudflare Pages 联系表单
Cloudflare Pages 只提供构建结果,没有表单收件箱。Pages Function 意味着你要维护 Worker 和邮件密钥。直接部署 HTML。浏览器把数据发到我们的主机。从 Netlify 迁走的团队,常常就在这一步失去 Netlify Forms。Cloudflare Pages 上的静态表单不需要 Pages Function,也不需要 wrangler 密钥。
Vercel 上的静态网站表单
Vercel 上的静态表单可以是 public 里的文件,或任何仍输出普通 HTML 的页面。不要把 action 指到一个你没写过的相对 /api。预览地址和生产地址都能用,因为接口不检查页面所在的主机。没有 Vercel 专用字段,无服务器函数也不是必需的。
Hugo、Jekyll、Astro 和 Eleventy 里的静态表单
生成器写出 HTML,但不提供收件箱。把代码放进布局,构建,再上传产物。访问密钥可以写在模板里,或在构建时注入一个允许公开的变量。不要把邮箱密码放进那个变量。构建后查看源代码,确认 action 还在。如果最终文件里没有这段代码,按钮就无法提交。

Hugo 联系表单
Goldmark 默认会转义 Markdown 里的原始 HTML,除非打开 unsafe。不必开。把静态表单放在 layouts/partials/contact.html,再从联系页布局调用。把 public 目录部署到任何主机。这是普通 HTML,和手写页面用的是同一段代码。
Jekyll 联系表单
Jekyll 布局里的静态表单没有 Liquid 标签,构建会原样留下它。仓库里有配置文件时,GitHub Pages 会跑 Jekyll。提交布局,从发布后的网址试一次。不要再放一个 contact.php。
Astro 联系表单
Astro 页面可以直接写这段 HTML。静态表单不需要 island。不必加 client:load,也不必在 src/pages/api 里加路由。放在 public 里的文件会原样复制。构建完成后,由浏览器提交这段代码。
Eleventy、Jamstack 表单,以及没有后端的 HTML 表单
Eleventy 从布局写出 _site。没有模板标签时,Nunjucks 和 Liquid 会放过这段代码。这里的 Jamstack 表单是构建出的文件加上托管接口,不是仓库里的函数。没有生成器时,静态表单就是任意 https 主机上的一个 contact.html。file:// 地址不能提交。
蜜罐和感谢页
静态表单的垃圾防护是蜜罐和一次短暂停顿。代码片段里有名为 botcheck 的字段,用 CSS 隐藏,并移出 Tab 顺序。人会留空。把每个输入都填上的脚本也会填它。接口会假装提交成功,然后丢掉它,什么都不保存。同一个 IP 和同一把密钥大约每五秒只能成功一次。静态表单的感谢页地址要在登录后保存在收件箱上。本机测试可以用 localhost 的 http。POST 里的跳转字段不能把浏览器带到别处。没有保存网址时,访客看到一页简短的托管感谢页。请求 JSON 时发送 Accept: application/json,或把 _reply 设为 json,页面不会跳转。

mailto、Netlify Forms 和 EmailJS
mailto 会打开邮件程序,而且经常发不出去。Netlify Forms 只在网站留在 Netlify 时有效。EmailJS 跑在页面里,公钥可以被拿去消耗额度。静态表单把数据发到一个接口,HTML 仍是你的,主机不必运行代码。
| mailto | Netlify Forms | EmailJS | Formpost | |
|---|---|---|---|---|
| 在哪里运行 | 访客的邮件程序 | 留在 Netlify 时 | 浏览器里的 JavaScript | 提交接口 |
| 你的网站要服务器吗 | 不要 | 不要,而且只能在 Netlify | 不要 | 不要 |
| 换主机之后 | 链接仍会打开邮件程序 | 表单收件箱留在原处 | 脚本还能跑 | 同一段代码 |
| 页面里的秘密 | 收件人地址 | 没有公开秘密,但绑在主机上 | 能消耗额度的公钥 | 只指向一个收件箱的访问密钥 |
| 访客看到什么 | 写信窗口 | Netlify 的成功页 | 脚本跑完后的页面 | 你的页面,然后是感谢页 |
| 垃圾控制 | 没有 | 他们的过滤 | 他们的额度和规则 | 蜜罐和短暂停顿 |
什么都没到的时候
一张新的静态表单如果收件箱是空的,通常表示 POST 已经完成、缺口在邮件,或者主机根本没让浏览器发出去。先查 action、蜜罐和垃圾箱,再改代码。
主机把提交吃掉了
静态表单如果刷新的是你自己的域名,请求就没有到达接口。action 是相对路径,或是平台属性截走了表单。Netlify 在带有 netlify 属性时就会这样。改成绝对提交地址,并确认请求发往 getformpost.com。
通知邮箱里什么都没有
核对通知邮箱,再用隐私窗口提交一次。sent 表示服务商接受了这封信。logged 表示邮件还没接通:新账户上这是正常的,不该留在公开网站。failed 表示保存成功但发送失败。页面里的密钥必须是当前这把。
垃圾邮件、蜜罐,以及用满的月份
在垃圾箱里搜收件箱名称和访客地址。如果有人说提交了,你却没有记录,多半是自动填充写进了 botcheck。代码片段已经把它隐藏。一个 UTC 月超过 100 条后,免费方案会锁定下一条,并且不把字段发邮件。Pro 每月 12 美元,5000 条提交,20 个收件箱。超过 5000 条会被拒绝。新账户有七天 Pro 额度,不需要银行卡。
接着读
父页面是网站联系表单。请求和锁住的跳转在表单发到邮箱。不含这些主机说明的标记在用 HTML 把联系表单发到邮箱。额度在价格和免费联系表单。那几页讲的是同一个静态表单接口。
常见问题
下面是第一位真实访客之后常见的情况:主机吞掉了提交、蜜罐误伤了人、感谢页没有打开,以及表单其实一直是好的。
从免费方案开始
创建账户,复制第一段代码。免费方案够放一张静态表单:每个 UTC 月 100 封邮件,一个收件箱。需要 5000 条或多于一个网站时,Pro 是每月 12 美元。额度在免费联系表单,结账在价格。
免费创建账户