The form we actually trust

Rhona MackayRhona Mackay Field Notes
28 May 2026
3 min

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 screenshot of the new form we designed, built and tested. 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 contact form's success state: "Thank you! We've received your submission and will reach out within 24 hours," followed by links to our Instagram and Labs, sitting below the form's privacy checkbox and Submit button. 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.