On 17 August 2026 we went through every form on our own website the way a client does: filled it in, submitted it, and then went to see what happened next. Not into the code and not at the thank-you screen, but to where the submission was supposed to end up: the mailbox, the email log, the server logs.
We found two subsystems that were not working. Outgoing webhooks failed on every submission. The confirmation email to the client was not being sent. And there was no sign of a fault from the outside: the form said “thank you,” the submission was saved in the database, the admin panel looked healthy.
This article is about why that happens and how to check your own form in 15 minutes without reading code.
What exactly was broken
Webhooks. These are notifications the site sends to another system when something happens: a new submission, a new brief, a new subscription. In our code, the list of events was written with one set of keys, while all the calls referred to it with different ones. The key being referenced did not exist, so instead of the event name an empty value went into the database query, and the query failed. There were 16 such places in the code: submission forms, pop-ups, the subscription and all six brief events. The file with the event list sat in the repository in this form from 20 January 2026 until the fix on 17 August.
Email. The project had two mail modules: an old one that could send email only through a single third-party service, and a new one that takes its settings from the admin panel, sends through our mail server and keeps a log. The import path in the code was the same for both, and the runtime picked the old file rather than the folder with the new module. The new module was written, but not a single email reached it. At the time of the check there was no key for the third-party service in the production environment, so every submission produced a line in the log saying the key was not configured, and the client was left without an email. The “send test email” button in the admin panel failed for the same reason.
That same day, continuing the investigation, we found two more things. Emails about commercial proposals had never been sent even once: the code took three functions from the old module that had never existed there. And four more places sent email bypassing the settings: the welcome email to a subscriber, the campaign mailing, the lead magnet and the weekly report.
Why nobody saw it
There are three reasons, and each on its own is common.
First: errors were caught and silently swallowed. Sending the email and the webhook was wrapped in error handling so that a mail failure would not break saving the submission. The decision itself is right: the submission matters more than the email. But after catching the error, it was only written to a log that nobody read.
Second: the email log itself did not work. The module that was supposed to record every send passed a value to the database that it did not accept, and that error was swallowed too. So the email log in the admin panel had no entries at all. An empty log is easy to read as “there have been no emails yet” rather than “the log is broken.” In the database, the first entry is dated 18 August 2026, the day after the fix.
Third: type checking saw some of these defects from the start, but the build ignores it. The missing functions for proposal emails were in the type-check report even before our changes. At the start of that session, type checking reported 374 errors, and 318 after two fixes. When there are hundreds of errors, nobody notices three more lines among them.
None of these reasons is about a particular person’s carelessness. It is a property of any subsystem that is not visible on screen: it can fail for months, and the site will still look perfectly healthy.
Who was actually affected
It is important not to overstate this. The webhook subscriptions table in our database is empty: no external system was subscribed to them, so in practice nobody lost any events. According to the same investigation, nobody had run any mass mailings. But at the time of the check, the confirmation email was not reaching a single client, and proposal emails could not be sent at all. How many emails were lost over the whole period we can no longer establish: the log that should have shown it was not working either.
You do not need to read code. You need to submit a request and follow its path to the end.
- Submit a request with a marker. In the name or comment field, enter something unique, for example
TEST-1609-contact-form. Use your real email address, not one you never open. Do this for each form separately: contacts, callback, pop-up, checkout, subscription.
- Check both sides. The confirmation email should have reached you as the client. The submission notification should have reached the manager. Look for the marker in “Inbox,” in “Spam” and in “Promotions.”
- Look at where the email came from. In Gmail, this is “Show original.” The
Authentication-Results line should contain spf=pass and dkim=pass. If it says fail or nothing at all, the email arrived today, but next time it may end up in spam.
- Find the traces on the server. If your system has an email log, the marker should be in it. If there is no log, search the logs:
grep -iE "mail|smtp|email" /path/to/error.log | tail -50
Lines like “not configured,” “timeout,” “connection refused” next to the time of your submission mean the email was not sent, even though the form said thank you.
- Count the sending paths. For technical readers, this is the most useful step. Search the code for every place that sends email:
grep -rnE "sendMail|mail\(|emails\.send|wp_mail|->send\(" --include=*.php --include=*.ts --include=*.js . | grep -v node_modules
If there is more than one place and they use different settings, you are in the same situation we were: you fix one form, and another one keeps staying silent.
- Check whether the log is empty. If the admin panel has an email log and it is empty on a live site that receives submissions, suspect the log first, not the absence of emails.
What we changed and what it did not solve
We made a single sending path for all emails: the one that takes its settings from the admin panel and records every attempt in the log. We removed the old third-party service client entirely so that nobody would use a second path again. We brought the webhook event keys into the form in which the code calls them. We fixed the log errors, and since 18 August 2026 it records every send.
What this did not solve was shown on 8 September 2026. That day, two subscription confirmation emails were not sent: the site did not manage to resolve the mail server’s address from its name within the allotted time. The log recorded this honestly. But nothing notified us: we have no alert for a failed email, and we found these two entries when we opened the log ourselves. Whether these were real subscribers or test sends, we do not know.
In other words, the lesson of the first part has been learned only halfway. Now we see failures if we look for them. The next step is for a failure to arrive as a notification on its own, the way alerts about server load already do. If you need someone to regularly go through your forms and check whether emails get delivered, that is part of website maintenance; the state of the server and the site itself is covered by uptime monitoring. We have already covered a similar story about a setting that “seems to work” using the example of page compression.
The main rule from all this is short: a subsystem that is not visible on screen is checked by sending, not by reviewing the code and not by a “thank you” message.