In February 2026, we replaced our contact form with a submission pipeline we built and own.
For the two years before that, it was a Pipedrive iframe inside our page. We had no control over fields, no client-side validation, no error handling beyond whatever Pipedrive showed inside its frame. When a submission failed, we found out from the client, not the system.
A form we built, designed and control.
Two forms, one pipeline
The start-a-project form is the full version: budget, brief, file upload, preferred dates, auto-detected timezone. It’s built as a Preact component with React Hook Form and Zod handling validation on the client. A minimal variant, just name, email and consent, runs on the AI Brand Engine page where a lighter touch makes more sense.
Both variants share the same submission pipeline. Different form surfaces, identical processing underneath. Every entry point gets the same validation, error handling and recovery, regardless of which page the inquiry came from.
The pipeline
The submission pipeline handles the sequence after the form is sent: validate with Google Recaptcha, create the contact and lead in our CRM, attach any uploaded files and send a confirmation email. Newsletter signups follow a shorter version of the same flow.
Both pipeline functions accept a typed dependencies object and every external service is injected rather than imported directly, which keeps the pipeline decoupled from its environment and makes each service boundary explicit. (How this enables testing is covered in a separate post.)
When things go wrong
If the lead is created but a secondary step like file upload fails, the submission still returns success to the user. The lead is safe. A structured alert fires to our team channel with enough context to recover manually.
Fatal errors, when the lead itself fails to create, alert the team with the same detail and rethrow so the user sees an error state. Nobody loses an inquiry silently.
Zod on both ends
The server-side actions run the same Zod schema again on the incoming data. Same validation, different boundary. A tampered request that bypasses client-side checks gets caught before it reaches the CRM.
From the user’s side
A form that validates as they type, shows clear error states when something’s wrong and lands on a confirmation screen with links to Labs and our Instagram, not a generic “thank you” page. They don’t know about the pipeline. They just know the form worked and the response didn’t feel like an afterthought.
The success screen — links to Labs and our Instagram, not a generic thank-you message.
Since the pipeline went live in February, every submission failure has alerted the team within seconds, with enough context to recover manually.
The framing board: every assumption about type we wanted to push on.
Typography wired to scroll speed: the faster you go, the more it frays.
The dial rebuilt in plain JS and CSS: a radial nav that drifts with scroll momentum.
“Magic”, revealed by hand-tracking through the webcam — no plugins, just the browser.
A few more from the room: a variable-font study, the 3D dial, a split-flap departures board, and type set into a landscape.