Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Sources/Db/Schema/v3_0/MailQueue.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,24 @@ public function __construct()
not_null: true,
default: 0,
),
'next_try' => new Column(
name: 'next_try',
type: 'int',
not_null: true,
default: 0,
),
'tries' => new Column(
name: 'tries',
type: 'tinyint',
not_null: true,
default: 0,
),
'extra' => new Column(
name: 'extra',
type: 'varchar',
size: 255,
not_null: false,
),
];

$this->indexes = [
Expand Down
87 changes: 60 additions & 27 deletions Sources/Mail.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,24 @@
*/
class Mail
{
/*****************
* Class constants
*****************/

/**
* Maximum number of tries to send a email.
*
* @var int
*/
private const MAX_TRIES = 15;

/**
* Multiplier for delaying emails that fail to send.
* See calculateNextTry() for implementation
* @var int
*/
private const DELAY_MULTIPLIER = 15;

/***********************
* Public static methods
***********************/
Expand Down Expand Up @@ -409,11 +427,13 @@ public static function reduceQueue(bool|int $number = false, bool $override_limi
$emails = [];

$request = Db::$db->query(
'SELECT id_mail, recipient, body, subject, headers, send_html, time_sent, private, priority
'SELECT id_mail, recipient, body, subject, headers, send_html, time_sent, private, priority, next_try, tries, extra
FROM {db_prefix}mail_queue
ORDER BY priority ASC, id_mail ASC
WHERE next_try <= {int:current_time}
ORDER BY priority ASC, next_try ASC, tries ASC, id_mail ASC
LIMIT {int:limit}',
[
'current_time' => time(),
'limit' => $number,
],
);
Expand All @@ -431,6 +451,9 @@ public static function reduceQueue(bool|int $number = false, bool $override_limi
'time_sent' => $row['time_sent'],
'private' => $row['private'],
'priority' => $row['priority'],
'next_try' => $row['next_try'],
'tries' => $row['tries'],
'extra' => $row['extra'],
];
}
Db::$db->free_result($request);
Expand Down Expand Up @@ -467,27 +490,8 @@ public static function reduceQueue(bool|int $number = false, bool $override_limi

// Send each email, yea!
$failed_emails = [];
$max_priority = 127;
$smtp_expire = 259200;
$priority_offset = 4;

foreach ($emails as $email) {
// This seems odd, but check the priority if we should try again so soon. Do this so we don't DOS some poor mail server.
if ($email['priority'] > $priority_offset && (time() - $email['time_sent']) % $priority_offset != rand(0, $priority_offset)) {
$failed_emails[] = [
$email['to'],
$email['body'],
$email['subject'],
$email['headers'],
$email['send_html'],
$email['time_sent'],
$email['private'],
$email['priority'],
];

continue;
}

if (empty(Config::$modSettings['mail_type']) || Config::$modSettings['smtp_host'] == '') {
$email['subject'] = strtr($email['subject'], ["\r" => '', "\n" => '']);

Expand All @@ -507,16 +511,25 @@ public static function reduceQueue(bool|int $number = false, bool $override_limi
}

// Old emails should expire
if (!$result && $email['priority'] >= $max_priority) {
if (!$result && $email['tries'] >= self::MAX_TRIES) {
$result = true;
}

// Hopefully it sent?
if (!$result) {
// Determine the "priority" as a way to keep track of SMTP failures.
$email['priority'] = max($priority_offset, $email['priority'], min(ceil((time() - $email['time_sent']) / $smtp_expire * ($max_priority - $priority_offset)) + $priority_offset, $max_priority));

$failed_emails[] = [$email['to'], $email['body'], $email['subject'], $email['headers'], $email['send_html'], $email['time_sent'], $email['private'], $email['priority']];
$failed_emails[] = [
$email['to'],
$email['body'],
$email['subject'],
$email['headers'],
$email['send_html'],
$email['time_sent'],
$email['private'],
$email['priority'],
self::calculateNextTry($email['tries']),
++$email['tries'],
$email['extra'],
];
}
}

Expand Down Expand Up @@ -565,7 +578,10 @@ public static function reduceQueue(bool|int $number = false, bool $override_limi
'send_html' => 'string',
'time_sent' => 'string',
'private' => 'int',
'priority' => 'int',
'next_try' => 'int',
'tries' => 'int',
'extra' => 'string',

],
$failed_emails,
['id_mail'],
Expand Down Expand Up @@ -1120,4 +1136,21 @@ protected static function userInfoCallback(array $matches): string

return $use_ref ? $ref : $matches[0];
}

/**
* Based on the number of tries, increase the time we delay the next sending.
*
* @param int $tries
* @return int Next time we should try to send.
*/
private static function calculateNextTry(int $tries)
{
$next = time();

for ($i = 0; $i < ($tries + 1); $i++) {
$next_send_time += $i * self::DELAY_MULTIPLIER;
}

return $next;
}
}