A user reported that signup didn’t work. They entered their email, filled in their name and password on step 2, hit Create Account, and nothing happened. No error message, no redirect, no feedback at all.
signup doesn’t seem to work. I enter email, next screen, name, company, password — hit create account and nothing happens
It turned out three separate bugs were hiding behind that one symptom.
Bug 1: Hardcoded database IDs
The signup flow created the user’s account, then seeded their workspace with sample content by copying existing records from the database:
$originalIdea = Idea::withoutGlobalScopes()->find(1261);
$newIdea = $originalIdea->replicate();
$newIdea->team_id = $company->id;
$newIdea->save();
Four specific records were hardcoded by ID: 1261, 1257, 1247, and 1287. If any of those records had been deleted — which they had — find() returns null and ->replicate() crashes. Since everything was wrapped in DB::transaction(), the crash rolled back the entire operation, including the user creation.
what are these hardcoded record IDs for?
They were seed content from early development. The fix was simple — remove them entirely. A new team gets a single default goal now, nothing else.
There was also User::find(2) — a hardcoded reference to a specific admin user who was auto-added to every new team. A null guard fixed this:
$stuart = User::find(2);
if ($stuart) {
$stuart->assignRole('Member');
Subscription::create([
'subscriber_id' => $stuart->id,
'team_id' => $company->id,
]);
}
explain what null guard is
A null guard is a safety check that says only run this code if the value isn’t null. User::find(2) returns either a User object or null. Without the guard, calling ->assignRole() on null crashes the entire signup.
Bug 2: Synchronous email sending
After fixing the hardcoded IDs, signup still didn’t work. Clicking Create Account produced a 502 Bad Gateway after 5-10 seconds.
The VerifyEmail notification had the Queueable trait but didn’t implement ShouldQueue:
class VerifyEmail extends Notification
{
use Queueable;
// ...
}
Without implements ShouldQueue, the email sends synchronously — the user waits while PHP connects to the mail server, negotiates SMTP, and sends the message. If the mail server is slow or misconfigured, the request hangs until nginx times out with a 502.
Before — user waits for the email to send:
class VerifyEmail extends Notification
{
use Queueable;
After — email is queued and sent in the background:
class VerifyEmail extends Notification implements ShouldQueue
{
use Queueable;
That single implements ShouldQueue means signup completes instantly. The email is dropped onto the queue and a background worker sends it separately. If it fails, the worker retries automatically.
I also wrapped the notification in a try-catch so that even if queueing fails for some reason, the user still completes signup:
try {
$result['user']->notify(new \App\Notifications\VerifyEmail);
} catch (\Throwable $e) {
Log::error('Signup verification email failed', [
'email' => $this->email,
'error' => $e->getMessage(),
]);
}
$this->currentStep = 3;
Bug 3: Validation error on the wrong step
Here’s the scenario that made this really confusing. The user goes through signup:
- Step 1: enters email, clicks Next
- Step 2: fills in name, company, password, clicks Create Account
- The request fails (because of bug 1 or 2)
- The user tries again — same email, same form
- This time,
unique:users,emailvalidation fails because the user was actually created before the crash - But the error message is on the
emailfield, which is only visible on step 1 — not step 2
So the user sees nothing. No error, no feedback. The validation error existed but was rendered on a field that wasn’t on screen.
The fix was adding an email error display to step 2:
<form wire:submit="submitForm">
<div class="flex flex-col gap-8">
<flux:error name="email" />
<flux:input label="Your full name" ... />
And moving the validation from a class-level $rules property to inline validation in submitForm(), so all fields are validated together at the point of submission:
public function submitForm()
{
$this->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|max:255|unique:users,email',
'password' => 'required|string|min:8|max:255|confirmed',
'company' => 'required|string|max:255',
]);
// ...
}
What I learned
The user saw one symptom — nothing happens. But three bugs were stacked on top of each other:
- Hardcoded IDs that silently crashed inside a transaction, rolling back everything
- Synchronous email that timed out the request before the user saw the success page
- Cross-step validation that showed errors on a step the user couldn’t see
Each one alone could have caused the same symptom. And because the transaction rolled back cleanly (no partial data, no error logged), there was no trail to follow. The signup just silently did nothing.
The fixes were individually small — a null guard, implements ShouldQueue, an error component on step 2. But finding them required tracing through the entire flow from button click to database write to email send to response.