Contact me
A bug report, a remark, a content suggestion? Write me a note.
If filling a form isn't for you, then most likely I'll see it on...
Setting up a contact form for free is kind of fun
- Why does a personal site that isn't selling anything need a contact form? Well, to give a way to any web surfer to contact the web admin, for another page on the already site, and at the end for fun. To see how things work, you know.
- It started while digging around Cloudflare during initial setup, and saw that it allows to create a free custom email address under your domain. Technically, Email Routing doesn't give you a mailbox, it simply forwards whatever arrives at that address to your personal inbox. Which is exactly what I'd enjoy to play around with.
- Then came the actual puzzle: how do I let people write to me without publishing the address anywhere for the ever-watching crawlers, agents, scrapers and scammers to collect? A
mailto:link is basically a gift to them. I could filter into oblivion whatever gets delivered there, but ain't that fun. A form, on the other hand, keeps the destination on the server side, where neither visitors nor the public repository ever see it - bingo. - Ready-made plugins and tools is what I was used to in the past when setting up Contact pages, especially on WordPress and Shopify. Install, play, done. From scratch, though, is such an alien world to me (sender domains, SPF, DKIM, DMARC, return paths, oof) that exploring it was joyfully enlightening.
- I was always wondering what other sections might be useful on a contact page, besides a map for a location when you're a store. The usual suspect is social media, which tends to repeat what's already in the footer (and on the About page in my case). WhatsApp was the obvious candidate, but it doesn't support linking by username yet (it's only now rolling out gradually), and exposing my mobile number is a no no. Messenger, on the other hand, allows linking by username quite easily, so here it goes.
- The other idea, for a future implementation: how to reveal an email address only to real people, without the usual checkbox-style checks. A mini game should do. Could an agent play it? Surely, if someone bothers to build one for a single small website. And that's kind of the point: a somewhat gaming experience isn't worth the effort for the generic scrapers. Will approach it with an experimental outlook, not sure how it can play out (hehe). UPDATE 12/10/26: And after a 2-day consideration, the implemnentation complexity is way higher than the fun or functionality of it. So, it's a no go. TBA WhatsApp it is.
- The page itself is still plain HTML, CSS and JavaScript. The only non-static piece is a single small Pages Function at
/api/contact, which runs on Cloudflare's edge when the form is submitted. No framework, no build step, no server to babysit and a castle to clean up. - When you hit send, the page posts the message as JSON through
fetchto that โ function. The function first verifies the Cloudflare Turnstile token server-side (the human check that rarely asks you to click on bicycles), and only then moves on to the rest: the request has to come from this domain, a hidden honeypot field has to be empty (bots love filling in everything), and the name, email and message are trimmed and validated against (generous or standard, dunno) length limits. - Complete name OR first name & last name, that is the question. For official stuff and large scale, the distinction between first and last names must exist. In this case, unecessery field.
- The email field got a few fun rules of its own. Only Latin characters are accepted there: anything else is stripped as you type or paste (even spaces), with a note suggesting to switch the keyboard layout, besides the nice auto-email part when you're typing on the phone. The server rejects them anyway, because the browser is just the polite first line of defense. The address is also lowercased once you leave the field, have seen to get lowercased during typing, but feels a bit off. In theory the part before the
@is case-sensitive, but in reality, every provider is treating it as if it weren't. - Delivery goes through Resend's API as plain text, so nothing a visitor types is ever interpreted as HTML, with the visitor's address set as Reply-to so that hitting reply just works. Visitor input is never placed in the sender, recipient or subject without being checked first.
- Resend's testing sender only delivers to the email you registered with, so it would have been enough for the form itself. But it's a sandbox, it would have meant using my personal address as the destination, and it can't send as my own domain. So I verified the domain instead (a few DNS records, SPF and DKIM, added in Cloudflare). Now messages go to the domain address, and I can reply as it from my regular mail client through Resend's SMTP, without my personal address showing up in the headers. I checked, and so glad that it works!
- The very first test email landed straight in Spam. After sorting out the DMARC record in Cloudflare, it showed up in the inbox. The world of email is full of wonders I guess?
- The repository is public, so nothing secret can live in it. The actual secrets (the destination address, the Resend API key and the Turnstile secret) are encrypted environment variables in Cloudflare Pages and never touch the code. The Turnstile site key is the only key in the repo, and it's public by default.
- Spent a tad bit more than a day on the design of this thing. It's a tweaked version of the lately revamped hero section on the homepage in form factor: same colours, same plain approach, nothing flashy. Hope it looks decent enough.
- And voila, until I figure out how to implement suggesting changes through GitHub pull requests on every page, this will do.
- Thanks Claude for
the helpgenerating the JS for the form. - Thanks Resend for the detailed documentation.
- Thanks Cloudflare for allowing in the first place to create domain emails for free โฅ
- I didn't expect the functional complexity of this simple page. Email is old-school misunderstood.