Your Contact Form Is Lying to You
Last week we caught a bug in our own contact form that had probably been there since we launched. A user submits the form, sees a success message, and moves on. Meanwhile, we receive nothing. No email, no notification. The inquiry evaporates.
The form wasn't broken in any visible way. It just lied.
What Was Happening
Our contact route in Next.js called resend.emails.send() but never checked the return value. The code looked roughly like this:
if (apiKey) {
const resend = new Resend(apiKey);
await resend.emails.send({ ... });
}
return NextResponse.json({ success: true });
Two problems here. First, if RESEND_API_KEY was missing from environment variables, the send was silently skipped. The route still returned { success: true } with a 200 status. Second, even with a valid key, if Resend rejected the send for any reason, we never checked the error. The await resolved, we ignored the result, and we returned success anyway.
From the user's perspective: their message was sent. From our perspective: nothing arrived.
The Fix
Resend's emails.send() returns { data, error }. If error is set, the delivery failed. We updated the route to actually check it:
if (!apiKey) {
console.error("[contact] RESEND_API_KEY is not set");
return NextResponse.json(
{ error: "Email service not configured" },
{ status: 500 }
);
}
const { data, error } = await resend.emails.send({ ... });
if (error) {
console.error("[contact] Resend send failed:", error);
return NextResponse.json(
{ error: "Failed to deliver message" },
{ status: 502 }
);
}
console.log("[contact] Email delivered, id:", data?.id);
return NextResponse.json({ success: true });
Now a missing API key returns 500. A Resend failure returns 502. The client-side can show an actual error instead of a false success, and we get logged output we can trace.
We also added replyTo: parsed.data.email to the send options. Before this fix, replying to a contact form notification meant copying the sender's email from the body text. Now one click opens a reply addressed to them directly.
Why This Is Easy to Miss
Contact forms feel like infrastructure. You wire them up once, test them in a dev environment where everything works, and move on. They're not a feature you keep eyes on after launch.
The problem is that API keys work in dev but can fail to propagate to production. Or Resend's domain verification lapses. Or a config change quietly breaks things months later. Without proper error surfacing, you have no idea.
The fix takes about fifteen minutes. Destructure the return value, check for an error, return a non-200 status when something goes wrong. Logging the delivery ID on success gives you a paper trail you can actually use.
The Broader Pattern
This is a version of the same mistake we see in a lot of small-business sites we inherit: third-party API calls wrapped in optimistic success responses. The form submits, the webhook fires, the integration triggers. But if any of those fail, the user sees a checkmark and the event is gone.
For contact forms, email delivery, payment confirmations, or anything where a failed request has real consequences: check the response, return the right status code, and log enough to debug later. The user experience for a real failure should be "try again or email us directly," not a false confirmation followed by silence.
We've tightened up our own checklist for this when building new projects. It's a small thing that matters more than it looks like it should.