mirror of
https://github.com/Patiun/com.gregkilmer.www.git
synced 2026-11-07 19:58:24 +00:00
Form Update
This commit is contained in:
+60
-1
@@ -25,9 +25,10 @@ delivery, set **either** SMTP vars **or** `CONTACT_WEBHOOK_URL` (see
|
||||
| `SITE_URL` | Canonical URL for metadata | `https://gregkilmer.com` |
|
||||
| `SMTP_HOST` / `SMTP_PORT` / `SMTP_USER` / `SMTP_PASS` | SMTP relay | unset |
|
||||
| `SMTP_FROM` | From address for outgoing mail | derived from `SITE_URL` |
|
||||
| `CONTACT_TO` | Inbox that receives submissions | `greg@gregkilmer.com` |
|
||||
| `CONTACT_TO` | Inbox that receives submissions | `contact@gregkilmer.com` |
|
||||
| `CONTACT_WEBHOOK_URL` | JSON webhook fallback (Discord/Slack/n8n) | unset |
|
||||
| `CONTACT_RATE_LIMIT` / `CONTACT_RATE_WINDOW_MIN` | Per-IP rate limit | `5` / `15` |
|
||||
| `TURNSTILE_SITE_KEY` / `TURNSTILE_SECRET_KEY` | Cloudflare Turnstile bot protection | unset (skipped) |
|
||||
|
||||
---
|
||||
|
||||
@@ -102,14 +103,72 @@ path:
|
||||
3. **Neither configured**: returns `503` with a friendly "email me directly"
|
||||
message so the form never silently swallows input.
|
||||
|
||||
### SMTP relay DNS requirements (homelab)
|
||||
|
||||
When pointing the form at a homelab SMTP relay (Postfix, Mailcow, etc.), the
|
||||
relay itself must have proper DNS records or mail **will** land in recipients'
|
||||
spam folders — this is the relay's responsibility, not the site's. Ensure the
|
||||
sending domain (the host part of `SMTP_FROM`, or the relay's HELO name) has:
|
||||
|
||||
- **SPF** (`TXT` on the domain): lists the relay's IP as an authorized sender.
|
||||
- **DKIM** (`TXT` on `selector._domainkey`): signs outgoing messages so the
|
||||
relay's identity can be verified.
|
||||
- **DMARC** (`TXT` on `_dmarc`): tells receivers how to handle SPF/DKIM
|
||||
failures (start at `p=none` to monitor, tighten to `quarantine` later).
|
||||
- **rDNS / PTR** on the relay's public IP: must match the relay's HELO name.
|
||||
|
||||
`SMTP_FROM` should use a domain you control (e.g. `no-reply@gregkilmer.com`),
|
||||
not `gmail.com` — SPF on a domain you don't own will reject or sink the mail.
|
||||
|
||||
Test delivery + spam score with [mail-tester.com](https://www.mail-tester.com/)
|
||||
(send a form submission to the unique address it gives you).
|
||||
|
||||
### Cloudflare Turnstile (bot protection)
|
||||
|
||||
The form includes an invisible Cloudflare Turnstile challenge. To enable it:
|
||||
|
||||
1. Create a widget at
|
||||
[Cloudflare Turnstile](https://dash.cloudflare.com/?to=/:account/turnstile).
|
||||
Use the **Managed** challenge mode (invisible, no puzzle).
|
||||
2. Put the **site key** in `TURNSTILE_SITE_KEY` and the **secret key** in
|
||||
`TURNSTILE_SECRET_KEY` (both in `.env`).
|
||||
3. The site key is public and ships to the browser; the secret stays
|
||||
server-side. The API verifies each token against Cloudflare's `siteverify`
|
||||
endpoint before sending mail.
|
||||
|
||||
If neither key is set, Turnstile is skipped entirely (useful for local dev —
|
||||
the form works without a challenge). The honeypot + per-IP rate limit still
|
||||
apply regardless.
|
||||
|
||||
Test it:
|
||||
|
||||
```bash
|
||||
# With Turnstile configured, this is rejected (no/bad token) with 400:
|
||||
curl -X POST http://localhost:4321/api/contact \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name":"Test","email":"test@example.com","message":"Hello there."}'
|
||||
|
||||
# Without Turnstile (keys unset), this succeeds and delivers mail:
|
||||
curl -X POST http://localhost:4321/api/contact \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name":"Test","email":"test@example.com","message":"Hello there."}'
|
||||
```
|
||||
|
||||
### Anti-scraping
|
||||
|
||||
The site applies a layered anti-scraping policy:
|
||||
|
||||
- **`public/robots.txt`** allows legitimate search engines (Google, Bing) but
|
||||
blocks known AI training bots (GPTBot, ClaudeBot, CCBot, Bytespider, etc.)
|
||||
and disallows `/api/` and `/contact` for everyone. Note: robots.txt is
|
||||
voluntary; determined scrapers ignore it.
|
||||
- **`noindex` on `/contact`**: the contact page emits
|
||||
`<meta name="robots" content="noindex, nofollow">` so it won't be indexed
|
||||
even if a bot reaches it.
|
||||
- **Email obfuscation**: the Gmail address is never in the HTML source as
|
||||
plaintext; it's base64-encoded and decoded to a `mailto:` client-side on
|
||||
click. Dumb harvesters that grep for `@` or `mailto:` find nothing.
|
||||
|
||||
---
|
||||
|
||||
## 5. Updating the site
|
||||
|
||||
Reference in New Issue
Block a user