Throttling Jobs in Laravel to Prevent API Flooding

Introduction

Laravel devs, here's a gem for you: πŸ’Ž

When sending emails with AWS SES, or any external API, it's crucial to ensure you don't flood the service with too many requests. This can be done effectively using the Redis::throttle method to manage rate limits. In this post, I'll show you how to throttle your jobs to keep your requests under control.

Why Throttle Your Jobs?

Throttling is essential when dealing with APIs that have rate limits to prevent being temporarily or permanently blocked. AWS SES, like many other APIs, has limits on the number of requests you can make in a given time frame. By throttling your jobs, you ensure that you stay within these limits and maintain a smooth operation.

Implementing Throttling with Redis

Let's implement a simple middleware that uses Redis::throttle to manage the rate limits for sending emails through AWS SES.

Step 1: Create the Middleware

First, we'll create the middleware using the artisan command:

php artisan make:middleware SesRateLimited

This will generate a new middleware class in app/Http/Middleware/SesRateLimited.php. We'll modify it as follows:

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Redis;

class SesRateLimited
{
    /**
     * Handle an incoming job.
     *
     * @param  \Illuminate\Queue\InteractsWithQueue  $job
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($job, Closure $next)
    {
        Redis::throttle('ses-rate-limited')
            ->block(2)->allow(10)->every(2)
            ->then(function () use ($job, $next) {
                $next($job);
            }, function () use ($job) {
                $job->release(30);
            });
    }
}

Explanation of Middleware:

  • Redis::throttle('ses-rate-limited'): This sets up a throttle with the key 'ses-rate-limited'.
  • ->block(2): Specifies that the job should wait up to 2 seconds to obtain a lock.
  • ->allow(10)->every(2): Allows up to 10 operations every 2 seconds.
  • ->then(function () use ($job, $next) { $next($job); }): If the lock is obtained, the job proceeds.
  • ->then(function () use ($job) { $job->release(30); }): If the lock is not obtained within the blocking time, the job is released back onto the queue to be tried again after 30 seconds.

Step 2: Create the Notification

Next, we'll create a notification class that extends Notification and implements ShouldQueue.

Create the notification class using the artisan command:

php artisan make:notification SendEmailNotification

Modify the SendEmailNotification class as follows:

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\MailMessage;
use App\Http\Middleware\SesRateLimited;

class SendEmailNotification extends Notification implements ShouldQueue
{
    use Queueable;

    protected $email;

    /**
     * Create a new notification instance.
     *
     * @return void
     */
    public function __construct($email)
    {
        $this->email = $email;
    }

    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return ['mail'];
    }

    /**
     * Get the mail representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return \Illuminate\Notifications\Messages\MailMessage
     */
    public function toMail($notifiable)
    {
        return (new MailMessage)
                    ->subject($this->email['subject'])
                    ->line($this->email['body']);
    }

    /**
     * Get the middleware the notification should use.
     *
     * @return array
     */
    public function middleware(): array
    {
        return [new SesRateLimited];
    }
}

Step 3: Create the Controller

Finally, dispatch the notification from a controller:

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\User;
use App\Notifications\SendEmailNotification;

class EmailController extends Controller
{
    public function sendEmail(Request $request)
    {
        // Example email data
        $emailData = [
            'subject' => 'Test Email',
            'body' => 'This is a test email sent using AWS SES.'
        ];

        // Get the user or notifiable instance
        $user = User::find($request->user_id);

        // Send the notification
        $user->notify(new SendEmailNotification($emailData));

        return response()->json(['message' => 'Email sent successfully.']);
    }
}

Conclusion

By implementing the SesRateLimited middleware and using Redis::throttle, you can efficiently manage your rate limits when sending emails with AWS SES or any other external API. This approach ensures that you stay within the API's limits and avoid any potential service disruptions.

Found this helpful?

If this guide was helpful to you, subscribe to my daily newsletter and give me a follow on X/Twitter. It helps a lot!

Subscribe to Harris Raftopoulos

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe