Diffinery

codician/diffinerypublic

Open-source, agent-first GitHub and GitHub Actions alternative that runs on your own Cloudflare account: git hosting, pull requests, code review and drop-in GitHub Actions YAML on Workers, Durable Objects, R2 and Containers.

Contact page: a public contact form with Turnstile, email confirmation and admin review #329

Mergedtest-botagenton behalf of cnbrkdmrcimerged into main from claude/contact-page
test-botagenton behalf of cnbrkdmrciopened Author

Requested by Can · project thread

What this changes

A public contact form at /contact, for people who aren't signed in (and those who are), and a Contact messages page in Site administration to read and manage what arrives.

  • The form: name, email, topic (a fixed list, Kumo select) and message. ?topic=press preselects a topic. Signed-in people skip the email field (their account's address is used), Turnstile and confirmation.
  • Protection (the early access form's, reused): Cloudflare Turnstile, honeypot, signed timing stamp, RL_AUTH per minute, daily limits (10 per network, 3 emails per address, 300 emails in total; 20 per signed-in person), at most 5 links, strict lengths, control and bidi characters stripped, plain text everywhere.
  • Double opt-in: a visitor's message stays unconfirmed until its address follows the emailed link (the auto-reply). That email never repeats anything typed, so the form can't relay spam to someone else's inbox.
  • Admins: each confirmed message emails every active site administrator (Organizations group, Reply-To the sender so they answer from their mailbox) and raises one in-app notification. /settings/admin/contact has New, Handled, Spam, Unconfirmed and All tabs, search, Reply by email, Mark handled, Spam, Reopen, a note for the other admins, and Delete. While an address has a message marked spam, its new messages are dropped silently.
  • API: GET/PATCH/DELETE /api/v3/diffinery/admin/contact[/{id}], site administrators only (404 for everyone else).
  • Retention: unconfirmed 7 days, spam 30 days, handled 365 days, limit rows (hashes only) 1 day.
  • Discoverability: footer link (visitors and signed in), About, FAQ, privacy policy, llms.txt, sitemap, IndexNow, ContactPage structured data; /contact/confirm is noindex and disallowed in robots.txt. contact is a reserved login.
  • Docs: new Contact form page, plus site administration, notifications, audit log, limits, rate limits, configuration and architecture.
  • Migration 0043 (contact_messages, contact_attempts), claimed in the migration registry.

How it was tested

  • pnpm -r typecheck and the unit tests pass (new test/unit/contact.test.ts: validation, opt-in, limits, spam blocking, admin changes, audit, retention).
  • New test/e2e/contact.test.mjs passes locally on wrangler dev (7 tests: links and SEO, the visitor flow with hostile input, signed-in flow, bots and oversized bodies, admin-only access, handle/spam/reopen/note/delete with audit, daily limits).
  • Checked by eye in light and dark, desktop and phone width, with no horizontal scroll.

Checklist

  • pnpm -r typecheck and pnpm -r test pass
  • User- or admin-visible changes update the docs in apps/diffinery/docs
  • New D1 migrations use the next free number in apps/diffinery/migrations

🤖 Generated with Claude Code

https://claude.ai/code/session_017TYj8AD92uS2DjQVGd59Fi

Intentwhat this change is meant to do

Give people who aren't signed in a safe way to reach the team, and give site administrators one place to read, answer and sort what they send.

test-bot merged commit bc4d1ba into main
Pull request successfully merged and closed
test-bot merged bc4d1ba into main
Sign in to join the conversation.