Skip to content

Commit c9c625e

Browse files
authored
Merge pull request #8804 from jdarwood007/3.0/MailQueueExtension
[3.0] Add additional fields to support better mail queueing
2 parents 17047c4 + b100be0 commit c9c625e

File tree

2 files changed

+78
-27
lines changed

2 files changed

+78
-27
lines changed

Sources/Db/Schema/v3_0/MailQueue.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,24 @@ public function __construct()
9292
not_null: true,
9393
default: 0,
9494
),
95+
'next_try' => new Column(
96+
name: 'next_try',
97+
type: 'int',
98+
not_null: true,
99+
default: 0,
100+
),
101+
'tries' => new Column(
102+
name: 'tries',
103+
type: 'tinyint',
104+
not_null: true,
105+
default: 0,
106+
),
107+
'extra' => new Column(
108+
name: 'extra',
109+
type: 'varchar',
110+
size: 255,
111+
not_null: false,
112+
),
95113
];
96114

97115
$this->indexes = [

Sources/Mail.php

Lines changed: 60 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,24 @@
2323
*/
2424
class Mail
2525
{
26+
/*****************
27+
* Class constants
28+
*****************/
29+
30+
/**
31+
* Maximum number of tries to send a email.
32+
*
33+
* @var int
34+
*/
35+
private const MAX_TRIES = 15;
36+
37+
/**
38+
* Multiplier for delaying emails that fail to send.
39+
* See calculateNextTry() for implementation
40+
* @var int
41+
*/
42+
private const DELAY_MULTIPLIER = 15;
43+
2644
/***********************
2745
* Public static methods
2846
***********************/
@@ -409,11 +427,13 @@ public static function reduceQueue(bool|int $number = false, bool $override_limi
409427
$emails = [];
410428

411429
$request = Db::$db->query(
412-
'SELECT id_mail, recipient, body, subject, headers, send_html, time_sent, private, priority
430+
'SELECT id_mail, recipient, body, subject, headers, send_html, time_sent, private, priority, next_try, tries, extra
413431
FROM {db_prefix}mail_queue
414-
ORDER BY priority ASC, id_mail ASC
432+
WHERE next_try <= {int:current_time}
433+
ORDER BY priority ASC, next_try ASC, tries ASC, id_mail ASC
415434
LIMIT {int:limit}',
416435
[
436+
'current_time' => time(),
417437
'limit' => $number,
418438
],
419439
);
@@ -431,6 +451,9 @@ public static function reduceQueue(bool|int $number = false, bool $override_limi
431451
'time_sent' => $row['time_sent'],
432452
'private' => $row['private'],
433453
'priority' => $row['priority'],
454+
'next_try' => $row['next_try'],
455+
'tries' => $row['tries'],
456+
'extra' => $row['extra'],
434457
];
435458
}
436459
Db::$db->free_result($request);
@@ -467,27 +490,8 @@ public static function reduceQueue(bool|int $number = false, bool $override_limi
467490

468491
// Send each email, yea!
469492
$failed_emails = [];
470-
$max_priority = 127;
471-
$smtp_expire = 259200;
472-
$priority_offset = 4;
473493

474494
foreach ($emails as $email) {
475-
// 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.
476-
if ($email['priority'] > $priority_offset && (time() - $email['time_sent']) % $priority_offset != rand(0, $priority_offset)) {
477-
$failed_emails[] = [
478-
$email['to'],
479-
$email['body'],
480-
$email['subject'],
481-
$email['headers'],
482-
$email['send_html'],
483-
$email['time_sent'],
484-
$email['private'],
485-
$email['priority'],
486-
];
487-
488-
continue;
489-
}
490-
491495
if (empty(Config::$modSettings['mail_type']) || Config::$modSettings['smtp_host'] == '') {
492496
$email['subject'] = strtr($email['subject'], ["\r" => '', "\n" => '']);
493497

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

509513
// Old emails should expire
510-
if (!$result && $email['priority'] >= $max_priority) {
514+
if (!$result && $email['tries'] >= self::MAX_TRIES) {
511515
$result = true;
512516
}
513517

514518
// Hopefully it sent?
515519
if (!$result) {
516-
// Determine the "priority" as a way to keep track of SMTP failures.
517-
$email['priority'] = max($priority_offset, $email['priority'], min(ceil((time() - $email['time_sent']) / $smtp_expire * ($max_priority - $priority_offset)) + $priority_offset, $max_priority));
518-
519-
$failed_emails[] = [$email['to'], $email['body'], $email['subject'], $email['headers'], $email['send_html'], $email['time_sent'], $email['private'], $email['priority']];
520+
$failed_emails[] = [
521+
$email['to'],
522+
$email['body'],
523+
$email['subject'],
524+
$email['headers'],
525+
$email['send_html'],
526+
$email['time_sent'],
527+
$email['private'],
528+
$email['priority'],
529+
self::calculateNextTry($email['tries']),
530+
++$email['tries'],
531+
$email['extra'],
532+
];
520533
}
521534
}
522535

@@ -565,7 +578,10 @@ public static function reduceQueue(bool|int $number = false, bool $override_limi
565578
'send_html' => 'string',
566579
'time_sent' => 'string',
567580
'private' => 'int',
568-
'priority' => 'int',
581+
'next_try' => 'int',
582+
'tries' => 'int',
583+
'extra' => 'string',
584+
569585
],
570586
$failed_emails,
571587
['id_mail'],
@@ -1120,4 +1136,21 @@ protected static function userInfoCallback(array $matches): string
11201136

11211137
return $use_ref ? $ref : $matches[0];
11221138
}
1139+
1140+
/**
1141+
* Based on the number of tries, increase the time we delay the next sending.
1142+
*
1143+
* @param int $tries
1144+
* @return int Next time we should try to send.
1145+
*/
1146+
private static function calculateNextTry(int $tries)
1147+
{
1148+
$next = time();
1149+
1150+
for ($i = 0; $i < ($tries + 1); $i++) {
1151+
$next_send_time += $i * self::DELAY_MULTIPLIER;
1152+
}
1153+
1154+
return $next;
1155+
}
11231156
}

0 commit comments

Comments
 (0)