Easy integration of Queue management into an existing Codeigniter 4 project

While working on a university information system we were encountering a phenomenon when some emails sent to users would fail to send. The university used Microsoft infrastructure for sending emails via SMTP, and well, for some esoteric reason one in 100 or so were failing to reach the recepient. Of course the failed emails needed to be resent, and Queue package seemed like the obvious choice to handle that.

Here is how we achieved the desired change basically without refactoring of the codebase.

First of all, we simply followed the Queue documentation to the letter in installing the package in our Codeigniter app and creating the Email queue files. You can read about that step here.

Now we had to decide where to tweak the code to send the failed emails to queue for sending retry later. There were a lot of code blocks in our code that looked like this:

$email = service('email');
$email->setFrom('noreply@lsu.lt', 'LSU informacinė sistema');
$email->setTo($user->email);
$email->setSubject($messageSubject);
$email->setMessage($messageBody);
$success = $email->send(false);

So the obvious choice at first seemed to add a code block that would check if email was successfully sent and if not – send it’s data to the queue:

if (! $success) {
    $emailData = [
        'recipients' => $user->email,
        'subject'    => $messageSubject,
        'body'       => $messageBody,
        'fromEmail'  => 'noreply@lsu.lt',
        'fromName'   => 'LSU informacinė sistema',
    ];
    service('queue')->push('emails', 'email', $emailData);
}

It would work fine, but this code had to be added in many different places.

So we decided to extend the Codeigniter 4 Email library instead. We added file app/Libraries/Email.php:

<?php

namespace App\Libraries;

use CodeIgniter\Email\Email as BaseEmail;
use ErrorException;

class Email extends BaseEmail {
    /**
     * Store the raw subject.
     *
     * @var string
     */
    protected $rawSubject = '';

    /**
     * Raw debug messages
     *
     * @var list<string>
     */
    private array $debugMessageRaw = [];

    /**
     * Indicates whether the email is being executed by the queue.
     *
     * @var bool
     */
    protected $isQueueExecution = false;

    /**
     * Indicates whether the email is being moved to the queue instead of sending.
     *
     * @var bool
     */
    protected $toQueue = false;

    /**
     * Spool mail to the mail server
     *
     * @return bool
     */
    protected function spoolEmail() {
        $this->unwrapSpecials();
        $protocol = $this->getProtocol();
        $method = 'sendWith' . ucfirst($protocol);

        if($this->toQueue){
            $this->addToQueue();
            return true;
        }

        try {
            $success = $this->{$method}();
        } catch (ErrorException $e) {
            $success = false;
            log_message('error', 'Email: ' . $method . ' throwed ' . $e);
        }

        if (! $success) {
            $message = lang('Email.sendFailure' . ($protocol === 'mail' ? 'PHPMail' : ucfirst($protocol)));

            log_message('error', 'Email: ' . $message);
            log_message('error', $this->printDebuggerRaw());

            $this->setErrorMessage($message);

            // Avoid recursive queueing
            if (! $this->isQueueExecution) {
                $this->addToQueue();
            }

            return false;
        }

        $this->setErrorMessage(lang('Email.sent', [$protocol]));

        return true;
    }

    /**
     * Returns raw debug messages
     * copy of the original method, which is set to private unfortunately
     */
    private function printDebuggerRaw(): string {
        return implode("\n", $this->debugMessageRaw);
    }

    /**
     * Set the queue execution flag.
     */
    public function setQueueExecution(bool $isQueue): void {
        $this->isQueueExecution = $isQueue;
    }

    /**
     * Set toQueue flag.
     */
    public function setToQueue(bool $toQueue): void {
        $this->toQueue = $toQueue;
    }

    /**
     * Add the failed email to the queue.
     */
    protected function addToQueue(): void {
        $queueData = [
            'recipients' => $this->recipients,
            'cc' => $this->CCArray,
            'bcc' => $this->BCCArray,
            // $this->subject results in empty string, had to make an owerride
            'subject' => $this->getRawSubject(),
            'body' => $this->body,
            'headers' => $this->headers,
            'fromEmail' => $this->fromEmail,
            'fromName' => $this->fromName,
            // 'attachments' => $this->attachments, // needs more work
        ];

        // Use queue library to add the data
        service('queue')->push('emails', 'email', $queueData);
    }

    /**
     * Override setSubject to store the raw subject.
     *
     * @param string $subject
     *
     * @return $this
     */
    public function setSubject($subject) {
        $this->rawSubject = $subject; // Store the raw subject

        return parent::setSubject($subject); // Call the parent method
    }

    /**
     * Get the raw subject.
     */
    public function getRawSubject(): string {
        return $this->rawSubject;
    }
}

The main change here is this: the extended class contains an overridden method spoolEmail(), which is responsible for sending emails; here, when an email fails to send, it automatically executes a new method addToQueue(),which sends the data of email to the queue for retrying later. (Some other changes needed to be added as well, they are explained in the comments of the code).

After this, we also added this code:

public static function email($config = null, bool $getShared = true)
{
    if ($getShared) {
        return static::getSharedInstance('email', $config);
    }

    if (empty($config) || (! is_array($config) && ! $config instanceof EmailConfig)) {
        $config = config(EmailConfig::class);
    }

    return new Email($config);
}

to app/Config/Services.php file, so that the Codeigniter’s Email service would use our extended class in place of the default Codeigniter’s Email class.

And that was it. Now, without any other changes to the old code, once an email fails to send, it is transferred to the emails queue and retries are made to send it later.

As you can see, if email is sent from the queue and fails, it is NOT readded to the queue, the code block

if (! $this->isQueueExecution) {
    $this->addToQueue();
}

takes care of that.

And if we have a cycle where a lot of emails need to be sent and we do not want the user to wait for those emails to send, we can call the method setToQueue() with parameter ‘true’ before the $email->send():

$email->setToQueue(true);
$email->send(false);

And, instead of sending the email immediately, it will be transferred to the queue to be dealt with later. And the user will be returned the page faster (as database operations are much faster than sending emails).

Here is how, with very little change in the code, we solved the problem of failed emails; and added a lot of scalability to our app.

Image credit goes to ChatGPT


Paskelbta

sukūrė

Žymos:

Komentarai

Parašykite komentarą

El. pašto adresas nebus skelbiamas. Būtini laukeliai pažymėti *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Raktažodžiai

atviras kodas bylos Codeigniter darbas drėlingas el. parašas EŽTT genocidas gimimas holokaustas InEnglish internetas joga Jurgelis karo nusikaltimai kde konferencija Kononov Kraujelis kubuntu LAT LGGRTC lietuvybė linux microsoft mokslai mokslas nusikaltimai žmoniškumui partizanai PHP pokaris programavimas programos religija religijos laisvė sausio13 sektos seneliai teismas teisė tinklaraštis vasiliauskas vertimas wordpress žurnalizmas

Vėliausi įrašai

Mano web projektai

Sąskaitos paprastai | Patobulinta juridinių asmenų paieška | Asmens kodų tikrinimo priemonė

Hobiai

Happysup.eu | Pawed Wave

Visuomenė, politika, etc.

Gentys | Religija.lt | Lietuvos religijotyrininkų draugija | Krizių įveikimo centras | GPB | BDS judėjimas

Tinklaraščiai

Kūlverstukas | Rimas Kudelis | Ar kas nors dar rašo tinklaraščius? 🙂

Technologijos

Codeigniter | HTMX | Alpine.js | Kubuntu

Mano viešasis PGP raktas
keybase.io paskyra

Autorinės teisės

© 2004-2024, Donatas Glodenis. Šiame tinklaraštyje paskelbtą autorinį turinį kitur galima naudoti tik gavus raštišką autoriaus sutikimą.

Jei konkrečiu atveju nėra nurodyta kitaip, tinklaraščio įrašuose išsakomi vertinimai yra asmeninė jų autoriaus nuomonė.