DEV Community

Cover image for I Made Cron and Queues Share the Work in Laravel — Here's My System
Elias Ahmed
Elias Ahmed

Posted on

I Made Cron and Queues Share the Work in Laravel — Here's My System

I used to think that cron jobs and queues are two different solutions, but now I realized they aren't competitors, they are meant to work together. I built a subscription system with automatic renewal billing called SubEngine, and found that integrating queues with cron jobs is highly efficient. Cron handles schedules. Queues handle events.

Here's how I split the work between the two, and why it remains resilient.

Cron vs Queues

  • Cron runs on a timer. "Every day at midnight, do X."
  • Queue runs on an event. "This just happened, go handle it."

Both seem like 2 different solutions to a problem, and that’s what it seems at first glance. But implementing the two together creates a highly efficient system, which I will explain why, and how I did it in SubEngine.

My System (SubEngine)

In SubEngine, every subscription with auto-renewal toggled gets a renewal attempt a day before it expires. Whether the renewal failed or succeeded, an email gets sent to the user. Here is how I split the work here:

The cron job's only task is to find who needs a renewal, and attempts
it. It doesn't send anything. Once the renewal attempt is
completed, that outcome, either success or failure, becomes an event. The event gets pushed to the queue, and the queue sends the email to the user.

Cron never touches email. Queue never touches renewal logic.
Two jobs, two responsibilities, no overlap.

Why This Split Makes a Difference

If cron sent the email directly, it must wait on your email provider to respond before moving to the next user. If the provider is slow or down, the entire renewal batch slows down or fails.

By pushing the email work to the queue, the cron stays remains fast and simple, while the queue can handle retries if the email fails, without ever touching renewal logic. Two systems that can fail independently without taking the each other down.

A Quick Note on Scaling

This system works exceptionally on a moderate number of users, but if you've got thousands of subscriptions renewing at once, the cron begins to slow down, and your back to one process doing too much.

The fix: Don't renew inside the cron job itself. Let the cron find who needs renewing, then dispatch a job to the queue per user:

Subscription::dueTomorrow()->lazy()->each(function ($subscription) {
    ProcessRenewal::dispatch($subscription);
});
Enter fullscreen mode Exit fullscreen mode

Now cron's only job is deciding who needs checking. The actual
renewal work happens in parallel, across your queue workers. Cron
gets faster, and your system scales without a rewrite.

Conclusion

Cron decides when. Queues decide what happens next. That split is what makes the system fast and reliable.

If you want to see a complete implementation, check out SubEngine. If you're solving this differently, I'd love to know your method.

Top comments (0)