There is a particular discomfort in running your own diagnostic over your own work. We build data systems for other people and spend a lot of time explaining that you cannot improve what you do not measure. Our own website had been live for weeks, looked good, and measured nothing at all.
So we ran it through the same audit we would run for a client. This is what the method looks like, and what it found.
The method: follow one record end to end
Most website audits are checklists — page speed, meta tags, mobile layout. Those are worth doing and they are not an audit. An audit follows a single record all the way through the system and asks, at every step, what happens if this step fails?
For a marketing site the record is a lead. Someone fills in the form. Where does that go? What is the first place it is durably stored? What happens if the next step throws?
That single question is what surfaced the real problem.
Finding one: the form had no memory
The contact route validated the submission and handed it directly to the email provider. There was no database write. Email was not the notification that a lead had arrived — email was the only copy of the lead.
The failure modes follow immediately:
- The provider errors, the route returns a 502, the lead is gone.
- No API key is configured, so the submission becomes a line of server log output — while the visitor is told "I'll personally email you within one business day."
Nobody had made a bad decision here. Someone had written the obvious code, in the obvious order, and the obvious order was wrong.
The fix is a rule, not a patch: persist first, deliver second.
1. Validate
2. Write the lead to Postgres ← the moment of capture
3. Return success based on THAT
4. Attempt email as a side effect, recording its own status
A total email outage now costs a notification, not a customer. The lead row
carries an email_status column, so "captured but not delivered" is a state the
system can see and report on — rather than a silence.
Finding two: attribution cannot be retrofitted
The form recorded which page it sat on, which was always the contact page. So the question "which page earned this lead" had no mechanism behind it.
This is the one finding with a deadline attached. Every lead captured before
those columns exist is permanently unattributable. You cannot backfill it, you
cannot infer it, and no amount of later work recovers it. Adding first-touch
UTM, referrer and landing path is about twelve lines of sessionStorage and two
columns.
If you take one thing from this: do the attribution capture before you need it. It is trivial today and impossible in six weeks.
Finding three: the checks that matter are for invisible failures
The site had been rendering internal author notes as visible body copy. A content field meant for the writer — a note to self about which bio still needed writing — was being pulled into the page and displayed to visitors. On a live page. For weeks.
Nobody caught it, because nothing looked broken. It rendered perfectly. It was simply wrong.
A convention ("don't render the notes field") is a thing people forget. So the build now runs an assertion over the rendered HTML — not the source, because a source grep misses the case that actually matters, which is a component pulling an unexpected field into the page.
That is the general principle worth stealing:
Write automated checks for the failures that are invisible when they happen. The loud failures will find you on their own.
A missing meta description, a fabricated metric, an unfilled template slot that still has its square brackets around it — none of these break anything. They just quietly cost you, indefinitely.
Finding four: rate limiting is a data-integrity problem
We added a per-IP submission limit, and then found the limit could be bypassed
by sending your own X-Forwarded-For header. The proxy configuration appended
the real address to whatever the client sent, and the application read the
leftmost entry — so a submitter could choose their own rate-limit bucket and
rotate it freely.
Worth noting why that matters beyond abuse: every duplicate and junk submission that gets through is a row in the table you will later count. Spam control is not only a security control. It is what keeps your conversion numbers meaning anything.
What we would do differently
Start with the ledger. If we had begun by asking "where is a lead durably written, and what is the query that counts them," every other decision would have followed from it — and the site would have been measuring itself from the first day it was live rather than from week six.
The uncomfortable part is that we know this. It is the advice we give. It is just much easier to see the gap in someone else's system than in your own, which is the entire argument for running the audit as a process rather than trusting that you would have noticed.
The five questions, if you want to run this yourself
- Follow one lead end to end. Where is the first place it is durably stored?
- If the step after that one fails, is the lead lost — and would you know?
- Can you answer "which page produced this lead" with a query? If not, are you capturing what you would need to?
- What on your site could be wrong right now without looking wrong?
- Which of your numbers would change if spam were counted properly?
None of these need a tool. They need an afternoon and a willingness to find something.