Implementing a load balancer (round robin) for email service on Laravel application

107 Views Asked by At

I have two services for sending emails, Postmark and SendGrid. I used Factory Pattern to be expandable and flexible.

EmailProviderInterface

namespace App\Services\Notifications;

interface EmailProviderInterface
{
    public function sendEmail(string $to, string $templateName, array $data);
}

PostMarkEmailService

<?php

namespace App\Services\Notifications;

use App\Traits\SendEmailTrait;

class PostmarkEmailService implements EmailProviderInterface
{
    use SendEmailTrait;

    /**
     * send email using postmark
     *
     * @param string $to
     * @param string $templateName
     * @param array $data
     * @return array
     */
    public function sendEmail(string $to, string $templateName, array $data): array
    {

        $templateInfo = $this->getHtmlRender($templateName, $data);
        if (!$templateInfo['success']) {
            return $templateInfo;
        }
        
        try {
            // implement logic calling external api and passing the html

            return $this->successHandler();
        } catch (\Exception $e) {
            return $this->handleErrors($e);
        }
    }
}

SendGridEmailService

<?php

namespace App\Services\Notifications;

use App\Traits\SendEmailTrait;

class SendGridEmailService implements EmailProviderInterface
{

    use SendEmailTrait;


    /**
     * Send Email Using SendGrid
     *
     * @param string $to
     * @param string $templateName
     * @param array $data
     * @return array
     */
    public function sendEmail(string $to, string $templateName, array $data): array
    {

        $templateInfo = $this->getHtmlRender($templateName, $data);
        if (!$templateInfo['success']) {
            return $templateInfo;
        }
        try {
            // implement logic calling external api and passing the html

            return $this->successHandler();
        } catch (\Exception $e) {
            return $this->handleErrors($e);
        }
    }
}

and this is EmailServiceProvider that will use it:

<?php

namespace App\Services\Notifications;

class EmailServiceProvider
{
    private EmailProviderInterface $emailProvider;

    public function __construct(bool $highPriority = true)
    {
        if ($highPriority) {
            $this->emailProvider = new PostmarkEmailService();
        } else {
            $this->emailProvider = new SendGridEmailService();
        }
    }

    /**
     * Send email using the email provider
     *
     * @param string $to
     * @param string $templateName
     * @param array $data
     * @return array
     */
    public function sendMail(string $to, string $templateName, array $data): array
    {
        return $this->emailProvider->sendEmail($to, $templateName, $data);
    }


}

I need to implement a load balancer (using a round robin strategy) with a retry mechanism. This would operate on the EmailService provider to swap between PostMark And SendGrid services.

If I were to implement a queue, how can apply such a load balancer?

1

There are 1 best solutions below

1
Enjoy Coding. On

To implement Load Balacing with retries in your EmailServiceProvider, you can update constructor and sendMail method.

<?php

namespace App\Services\Notifications;

class EmailServiceProvider
{
    private array $emailProviders;
    private int $currentProviderIndex;

    public function __construct()
    {
        $this->emailProviders = [
            new PostmarkEmailService(),
            new SendGridEmailService(),
        ];
        $this->currentProviderIndex = 0;
    }

    public function sendMail(string $to, string $templateName, array $data): array
    {
        $maxRetries = count($this->emailProviders);

        for ($retry = 0; $retry < $maxRetries; $retry++) {
            try {
                $provider = $this->emailProviders[$this->currentProviderIndex];
                $response = $provider->sendEmail($to, $templateName, $data);
                return $response;
            } catch (\Exception $e) {
                $this->rotateProvider();
            }
        }

        return ['success' => false, 'message' => 'Failed to send email'];
    }

    private function rotateProvider(): void
    {
        $this->currentProviderIndex++;
        if ($this->currentProviderIndex >= count($this->emailProviders)) {
            $this->currentProviderIndex = 0;
        }
    }
}

You can also implement load balancing at the queue level by leveraging a message broker such as RabbitMQ or Redis.

These message brokers support load balancing mechanisms out of the box, allowing you distribute the processing of email jobs across multiple worker processes or machines.