Forms and search on your server
Set up the endpoint, the small program that sends your site's form submissions by email and can answer searches. For whoever runs the server.
Your site is plain files, which can't send email. The endpoint is a small program you run on your web server, beside the site, that receives form submissions, checks them, and emails them to you. It can also answer searches. It holds no content, changes nothing, and needs no database.
What you need
- Your web server (nginx, Caddy or similar), with HotDog CMS installed on it.
- An email account it can send from, over SMTP: your email provider's settings for sending mail (server, port, user name and password).
- Your site's folder on the server (its
site.yamlandforms/), and the built site.
1. Write endpoint.yaml
listen: 127.0.0.1:8180
trusted_proxies: [127.0.0.1/32] # your web server, so it knows visitors' addresses
smtp:
host: smtp.example.org
port: 587
username: forms@example.org
from: "Bakery on Main <forms@example.org>"
tls: starttls # or tls, on port 465
rate_limit: { count: 5, window: 10m } # per visitor
sites:
- dir: /srv/sites/bakeryonmain # the site's folder: site.yaml and forms/
public: /var/www/bakeryonmain # the built site, for search
store: /var/lib/hotdog-cms/forms/bakeryonmain # only for forms with store: true
recipients: [hello@bakeryonmain.example] # the only addresses its forms may send to
recipients is a safety net: even if someone changed a form's to:, it can only send to these addresses. "@bakeryonmain.example" allows any address there.
2. Start it, with its two secrets
export HOTDOG_FORM_SECRET=$(openssl rand -hex 32)
export HOTDOG_SMTP_PASSWORD='your email password'
hotdog-cms endpoint -config endpoint.yaml
HOTDOG_FORM_SECRET signs the token each form page fetches, so keep it the same across restarts, and on every server serving the same site. Run it as a service, like the pull agent, with the secrets in an EnvironmentFile.
3. Pass /_hotdog/ to it
In nginx, inside your site's server block:
location /_hotdog/ {
proxy_pass http://127.0.0.1:8180;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
In Caddy: reverse_proxy /_hotdog/* 127.0.0.1:8180.
How it keeps spam and abuse out
Every submission must pass, in order:
- It came from your site's own page.
- A rate limit, per visitor: 5 in 10 minutes unless you change it.
- Text only, no files, at most 64 KB.
- A hidden trap field that people never see and robots fill in. Robots are told their message was sent.
- A signed token, between 3 seconds and 24 hours old, which the page fetches when it opens. That stops instant, scripted posts.
- Cloudflare Turnstile, if the form asks for it (
turnstile_sitekey). - The fields themselves: only the declared ones, required ones filled, lengths within limits, email addresses that are real addresses, choices from the list, no line breaks in one-line fields.
Then it emails the submission, with Reply-To set to the sender, so you can just reply. If email fails and the form keeps a copy (store: true), the submission is kept and the visitor told it worked.
Search
Collections with search: true in site.yaml get a search index. A search box can use it in the browser (works on any host), or ask the endpoint (/_hotdog/search/<collection>?q=…), so visitors download only the results.
Testing on your computer
Run the endpoint locally with the settings above (on 127.0.0.1:8180), then:
hotdog-cms serve -endpoint http://127.0.0.1:8180
The preview passes /_hotdog/ to it, as your web server would.
Reading submissions in the editor
Forms with store: true keep each submission on the server. The editor's Forms page can show them, through a second, private listener of the endpoint that only answers on a private network and with a token. That's set under viewer: in endpoint.yaml and submissions: in editor.yaml; see the forms guide for both.

