PHP Laravel

Automating Mentorship Contract Lifecycle in Laravel: From Notification to Re-Enrollment

Managing ongoing subscriptions and contracts can be a delicate balance between providing a seamless user experience and ensuring robust system automation. How do you gracefully handle contract expirations, notify users proactively, and facilitate re-enrollment without extensive manual oversight? In the landing project, we recently tackled this challenge for our 6-month mentorship contracts.

The Challenge: Manual Contract Management

Previously, managing the lifecycle of these 6-month mentorship contracts involved a degree of manual intervention, especially around expiration and re-enrollment. Without clear automated processes, there was a risk of mentees missing renewal deadlines, leading to unexpected service interruptions or an unclear path to continue their mentorship. Our goal was to build a fully automated system that handles notifications, expiration, and ensures a smooth re-enrollment path.

The Automated Solution: Scheduled Commands to the Rescue

Our solution leverages Laravel's robust scheduling capabilities to manage the entire contract lifecycle automatically. This involves two key scheduled commands and updates to the user interface and database schema.

7-Day Reminder

One week before a mentee's 6-month contract is set to expire, a daily scheduled command, mentorship:notify-contract-ending, springs into action. This command identifies all paid subscriptions ending in the next 7 days and sends a clear re-enrollment call-to-action via email. To ensure idempotency and prevent duplicate notifications, a new ending_notice_sent_at column tracks when this reminder has been issued.

Expiration Logic

On the actual contract end date, another daily scheduled command, mentorship:expire-contracts, takes over. Running in the early hours, this job performs several critical tasks:

  • Marks Subscription as Cancelled: The mentee's subscription status is updated to Cancelled.
  • Cancels Future Bookings: Any future mentorship bookings associated with the now-expired contract are automatically cancelled.
  • Sends Expiration Email: A final email is sent to the mentee, confirming the contract's expiration and reiterating the re-enrollment opportunity.
  • Ignores Free Subscriptions: Crucially, free subscriptions (those without a defined contract_ends_at date) are intentionally bypassed by this command, as they operate on a different lifecycle.

User Experience: Dashboard Banners

To keep mentees informed, the bookings dashboard now displays prominent banners for contracts that are either ending soon or recently expired. This provides immediate visual cues, guiding users towards the next steps.

Seamless Re-Enrollment Flow

Post-expiration, the system is designed to allow mentees to re-enroll seamlessly. Since their old subscription is marked Cancelled, the existing enrollment flow correctly identifies them as eligible to start a fresh 6-month contract, bypassing any "already enrolled" gates.

Architectural Overview

This system integrates several components:

  • Scheduled Commands: Two new Artisan commands (mentorship:notify-contract-ending and mentorship:expire-contracts) handle the core logic.
  • Mailables: Dedicated Mailable classes (MentorCycleContractEndingNotification and MentorCycleContractExpired) manage the content and sending of notification emails.
  • Controller Updates: The MentorBookingController::index method was extended to query and surface the contract status to the dashboard view.
  • Database Schema: New columns (contract_expired_at, ending_notice_sent_at) were added to the mentor_subscriptions table for precise tracking.

Here's a simplified example of how a scheduled command might be defined in Laravel:

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Models\MentorSubscription;
use App\Mail\MentorCycleContractEndingNotification;
use Illuminate\Support\Facades\Mail;
use Carbon\Carbon;

class NotifyContractEnding extends Command
{
    protected $signature = 'mentorship:notify-contract-ending';
    protected $description = 'Notifies mentees about contracts ending in 7 days.';

    public function handle()
    {
        $sevenDaysFromNow = Carbon::now()->addDays(7)->startOfDay();

        MentorSubscription::whereNotNull('contract_ends_at')
            ->whereDate('contract_ends_at', $sevenDaysFromNow)
            ->whereNull('ending_notice_sent_at')
            ->chunk(100, function ($subscriptions) {
                foreach ($subscriptions as $subscription) {
                    Mail::to($subscription->mentee->email)
                        ->send(new MentorCycleContractEndingNotification($subscription));

                    $subscription->update(['ending_notice_sent_at' => Carbon::now()]);
                    $this->info("Notified mentee {$subscription->mentee->id} about ending contract.");
                }
            });

        $this->info('Contract ending notifications processed.');
    }
}

Key Takeaways

Automating contract lifecycle management provides significant benefits. It reduces operational overhead, ensures timely communication with users, and creates a smoother experience for both mentees and administrators. By leveraging scheduled tasks and careful state management, we've built a robust system that handles the complexities of subscription renewals with minimal human intervention.

Automating Mentorship Contract Lifecycle in Laravel: From Notification to Re-Enrollment
GERARDO RUIZ

GERARDO RUIZ

Author

Share: