Backend: secure contact API
A small Fastify service that blocks bot spam, keeps inbox credentials off the page, and stores every lead before mail goes out
Backend & security
Keep your marketing site static.
Keep your inbox off the public internet.
A TypeScript Fastify contact API that checks an ALTCHA proof-of-work, validates and saves each submission in PostgreSQL, then sends mail over SMTP. The company address and mail credentials stay on the server. The browser never sees them.
Problem
Contact forms either leak secrets or invite spam
A marketing site needs a way for real people to reach you. The usual shortcuts create new problems: a mailto: link puts the address in every scraper's path, posting mail from the browser would require shipping SMTP credentials to the client, and a third-party form widget adds another vendor, another captcha, and another outage risk. We needed a path that keeps the frontend static-friendly while every sensitive setting lives only on the server.
Approach
Two endpoints. One job for the browser: prove and post.
The API surface is intentionally small. GET /challenge returns a signed ALTCHA challenge the widget solves as proof-of-work. POST /contact accepts JSON with name, email, subject, body, and the ALTCHA solution. The marketing app stays a few fetch calls; the service owns verification, storage, and mail.
What the frontend sends
POST /contact
Content-Type: application/json
{
"name": "Alex Visitor",
"email": "visitor@example.com",
"subject": "Project inquiry",
"body": "Message text…",
"altcha": "<signed solution from GET /challenge>"
}What the server checks first
Bodies are validated with explicit rules: required fields, minimum lengths after trimming, a real email check, and sanitization that strips null bytes and most control characters while keeping newlines and tabs. The ALTCHA payload is verified with a shared secret before any database write or outbound mail. CORS is limited to the configured site origin so only the real frontend can call it from a browser.
Approach
Store the lead first. Then notify the company inbox.
Accepted submissions are inserted into a PostgreSQL emails table (via Drizzle ORM) before any message leaves the process. That gives you a durable record even when delivery has a bad day. Outbound mail uses Nodemailer with environment-driven SMTP settings. The transporter is verified before send so bad credentials fail loudly at startup or first delivery instead of silently dropping inquiries.
What ships to the team today
A plain-text notification goes to the configured company inbox, using the form subject line and putting the visitor's name, email, and message in the body (there is no separate Reply-To header; reply using the address in the text). A rate-limited confirmation email to the submitter is implemented in the service but is not enabled in production yet.
Results
What this unlocks for a marketing site
- Proof-of-work cuts automated spam without embedding a third-party captcha widget on every page.
- The recipient address and SMTP credentials never reach the client, so you can rotate keys and swap providers without republishing the frontend.
- Validation and verification failures return predictable JSON; operational issues surface as generic 4xx/5xx to the browser.
- Every accepted submission is stored, so inbound interest remains reviewable for support and abuse even if mail delivery hiccups.
Lesson
Public endpoints need a fast happy path and a boring failure path
Contact APIs sit on the open internet. The design bias here was toward explicit contracts, early validation, and a reliable path to the team inbox: persist the lead, then deliver mail, without putting secrets in the browser. The same pattern fits other small public services (webhooks, lead forms, light integrations) where secrets and deliverability matter more than feature surface area.
How we work
Tight scope, explicit contracts, and shipping code you can operate.
Shape
Define endpoints, payloads, and what “done” means for security and deliverability.
Harden
Validation, configuration boundaries, and logging that helps you debug without exposing secrets.
Integrate
Wire the frontend or static site to the API and verify end-to-end in a realistic environment.
Hand off
Document env vars and runbooks so your team can own the service long term.
Need a focused API like this?
A fit when you need a contact pipeline, webhook receiver, or JSON API with TypeScript, clear validation, and deployment-friendly configuration. You keep ownership; we leave env docs and a runbook your team can operate.
Tell us about the endpoint you need — contact forms, integrations, or hardening an existing public route. We will reply with scope questions and next steps.