How a form submission finds the right tool
The submission is checked and routed-ready. Now dispatch has to get it to the right places — the team that handles it, the CRM or sheet it belongs in, the customer who’s waiting on a reply. Get this wrong and the lead is worse than lost: it goes to the inbox nobody reads, or it vanishes because the CRM was down for ninety seconds. Four small gates sit between “routed-ready” and every delivery actually landing — and each delivery runs on its own so a slow tool can’t hold up the rest.
Key takeaways
- The routing table maps each form to a team email, a CRM list or sheet tab, and an auto-reply template.
- Dispatch fans the submission out into separate delivery jobs — team email, CRM write, sheet write, customer reply.
- Each delivery is its own job on an SQS queue with retries and growing backoff.
- A slow CRM never delays the customer reply — the jobs are independent.
- Every delivery is logged so the next step knows it landed and won’t double-send.
Four gates between routed-ready and delivered
Gate 1: resolve the route
Dispatch starts by looking up the routing rule for the submission’s form_id (and category, for the catch-all contact form) in the routing table. The table is a Google Sheet your team can edit, mirrored to S3 every fifteen minutes by a small sheet-sync Lambda — so dispatch reads a fast local copy, never the live sheet, on every submission. Each row says four things: which team email gets it, which CRM list or sheet tab the row goes into, which auto-reply template the customer receives, and an optional priority flag for forms that should also page someone.
Because the table is a sheet, the day sales splits into “new business” and “renewals,” or you add a careers form, the owner edits a row and the next submission routes the new way. No code change, no deploy, no waiting on an engineer.
Gate 2: fan out into independent jobs
This is the gate that protects you from slow tools. Instead of doing all the deliveries one after another in a single function — where a slow CRM would delay the team email and the customer reply behind it — dispatch turns the one submission into several separate jobs and drops each onto an SQS queue. A queue is just a managed waiting line for jobs. There’s a job to email the team, a job to write the CRM, a job to append the sheet row, and a job to send the customer’s auto-reply. Each is picked up and run on its own.
Independence is the whole benefit. The customer’s “we got it” reply doesn’t wait behind the CRM. The team email doesn’t wait behind the sheet. If three deliveries succeed instantly and one is slow, three land instantly and the fourth catches up on its own clock.
Gate 3: queue and retry
Each job is processed by a small worker Lambda that does exactly one delivery. If the downstream tool answers cleanly, the job is done. If it returns an error or times out — the CRM is mid-deploy, the email service hiccupped, the sheet API is briefly rate-limited — the job isn’t lost. SQS hands it back after a wait and the worker tries again, with the gap growing each time (a few seconds, then tens of seconds, then minutes). Most transient failures clear within a try or two, and the delivery lands without anyone noticing there was a problem.
This is the heart of “never drops the lead.” A normal form-handler that emails inline has exactly one chance: if the send fails, the lead is gone. A queued job has many chances spread over time, and the submission sits safely in storage the whole while. What happens if a tool stays down past every retry is the subject of Part 5 — the dead-letter queue — but the short version is that even then, nothing is deleted.
Gate 4: confirm and log
When a delivery succeeds, the worker writes a row to the fir-deliveries table in DynamoDB recording which submission, which delivery (team_email, crm, sheet, customer_reply), and when. Two things fall out of this. First, the system can prove a lead was delivered — the audit answer to “did sales actually get this?” is a lookup, not a shrug. Second, because each job checks the log before acting, a job that gets retried after it had actually already succeeded (which queues occasionally do) won’t send the team a second copy or write the CRM row twice. The delivery is recorded as done, so the retry is a no-op.
Why the gates exist
None of these gates are exotic. They’re the care a thoughtful person would take by hand — look up where this should go, do each errand separately so a slow one doesn’t hold up the rest, try again if a delivery bounces, and write down what you’ve done so you don’t do it twice. Putting them in code as four small steps makes them part of the design, not a feature you’re trusting a fragile inline send to get right under pressure.
Next post: how a submission gets confirmed end to end — the customer reply, the de-duplicate guard, the retry budget, and the dead-letter queue that catches anything a downstream tool simply refuses to accept.
All posts