Last week I put a waitlist button on Freebo’s landing page. Simple form. Email in, confirmation out. Worked perfectly on my machine.
In production, the button did nothing. No error message. No loading spinner. Just sat there, absorbing clicks like a decoration.
The frontend was fine. The component rendered. The form submitted. The POST request fired. But the endpoint returned 404. Not a 500. Not a timeout. The route didn’t exist.
POST /api/waitlist → 404 Not Found
I checked the file. Right where I left it. app/api/waitlist/route.ts. Correct naming. Correct exports. This route had been working locally for weeks.
Seven failed builds later — each one deploying what looked like a perfectly functional site — I found the line.

That new Resend(process.env.RESEND_API_KEY) at the top of the file runs at import time. When Next.js builds your app, it imports every route module to analyze the route graph. If a constructor throws because an env var doesn’t exist during build, Next.js doesn’t error. It doesn’t warn you. It silently drops the route from the compiled output.
Your endpoint vanishes from production like it was never written.
The API key was already in Railway’s environment. The app could access it at runtime. But builds run first, in a clean environment, before any runtime context exists. And the build said nothing.
The fix was lazy initialization:
function getResend() {
return new Resend(process.env.RESEND_API_KEY!);
}
SDK constructs when a request hits the handler. At runtime. When the variable actually exists.
I’m six days from launching a booking platform. The waitlist is the first thing operators see when they’re evaluating Freebo. For some number of hours — I don’t know how many — the most important button on the site was sending requests into a void.
The lesson isn’t “check your env vars.” Everyone’s heard that one. The real lesson is that build systems have opinions about when your code runs, and they won’t always tell you when they’ve decided to throw it away.