Fixing Early Birds: How We Refined Mentor Booking Schedules
In the devlog-ist/landing project, which manages various aspects of our educational offerings, we recently tackled a subtle but critical bug related to mentorship scheduling. Imagine signing up for a mentorship program only to receive reminder emails for sessions weeks before the program officially begins. That's exactly the kind of premature notification we aimed to eliminate.
The Problem
Our system, responsible for generating future MentorBooking slots, wasn't always respecting the starts_at date configured for a MentorshipService. Specifically, the MentorScheduleExpander::expand() method would sometimes generate bookings from an earlier from date, ignoring the service's actual start time. This meant that while the service itself hadn't begun, the system would process these 'ghost' bookings, triggering various workflows prematurely. This directly impacted crucial enrollment flows, including free cycle enrollment, paid enrollment finalization (especially through Stripe webhooks), and invoice renewals, leading to confusing and incorrect communications with users.
The Solution
The core of the issue lay in the iteration logic within our MentorScheduleExpander::expand() method. While another related method, MentorshipService::getAvailableSlots(), already correctly factored in service.starts_at, the expander was missing this crucial step. The fix involved adjusting the cursor's starting point within the expand() method. Instead of simply starting from a given from date, the expander now advances its internal cursor to max(from, service.starts_at). This ensures that no bookings can ever be generated for dates preceding the service's actual start date.
Implementation Details
This conceptual PHP snippet shows how currentDate is initialized. By taking the maximum of the requested from date and the service->starts_at date, we guarantee that the iteration for generating future bookings always begins on or after the service's official start. This simple yet effective change aligns the booking generation with the intended service lifecycle.
class MentorScheduleExpander
{
public function expand(DateTimeImmutable $from, MentorshipService $service, int $slotsToGenerate): array
{
$bookings = [];
// Ensure we never generate bookings before the service actually starts
$currentDate = max($from, $service->starts_at);
while (count($bookings) < $slotsToGenerate) {
// Logic to find next available slot based on $currentDate and service schedule
if ($this->isAvailable($currentDate, $service)) {
$bookings[] = new MentorBooking($currentDate, $service);
}
$currentDate = $currentDate->modify('+1 day'); // Or other increment logic
}
return $bookings;
}
// ... other methods ...
}
Impact and Benefits
This seemingly small adjustment has a significant impact on user experience and system reliability. It prevents users from receiving confusing, out-of-sync notifications and ensures that all system-triggered events—such as reminder emails or Stripe payment processing flows—are correctly aligned with the actual mentorship schedule. This improves the overall integrity of our scheduling system and enhances trust in our communication processes.