fix(email): recover stuck queue — reinit transporter on config save + manual flush ignores retry cap
Two gaps left emails stuck 'pending' after (re)configuring SMTP: 1. Saving the email config never re-initialised the transporter. The queue processor only re-inits when its cached transporter is null, so a changed SMTP account had no effect until a backend restart. Now call initializeTransporter(true) after save (it self-catches; invalid config just leaves it null, surfaced via the Test-email button). 2. The manual 'send now' flush (ignoreSchedule) still enforced retry_count<3, so emails that failed 3× while SMTP was broken could never be retried from the UI. Move the retry-cap (and schedule gate) to automatic runs only; a manual flush forces a retry of every pending email.
This commit is contained in:
@@ -94,6 +94,16 @@ router.post('/config', [
|
|||||||
await db('email_configs').insert(configData);
|
await db('email_configs').insert(configData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refresh the cached transporter so the new SMTP settings take effect
|
||||||
|
// immediately. Without this, a previously-initialised transporter stays
|
||||||
|
// cached (the queue processor only re-inits when it's null), so changing
|
||||||
|
// the email account had no effect until a backend restart — emails kept
|
||||||
|
// failing against the old/empty config. initializeTransporter catches its
|
||||||
|
// own errors and returns null, so this never throws; an invalid config
|
||||||
|
// simply leaves the transporter null (surfaced via the Test-email button).
|
||||||
|
const { initializeTransporter } = require('../services/emailProcessor');
|
||||||
|
await initializeTransporter(true);
|
||||||
|
|
||||||
// Log activity
|
// Log activity
|
||||||
await logActivity('email_config_updated',
|
await logActivity('email_config_updated',
|
||||||
{ smtp_host, from_email },
|
{ smtp_host, from_email },
|
||||||
|
|||||||
@@ -774,13 +774,18 @@ async function processEmailQueue({ ignoreSchedule = false, limit = 10 } = {}) {
|
|||||||
// queue split-payment emails relative to the event date.
|
// queue split-payment emails relative to the event date.
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const query = db('email_queue')
|
const query = db('email_queue')
|
||||||
.where('status', 'pending')
|
.where('status', 'pending');
|
||||||
.where('retry_count', '<', 3);
|
|
||||||
if (!ignoreSchedule) {
|
if (!ignoreSchedule) {
|
||||||
query.andWhere(function() {
|
// Automatic runs: respect the retry cap (don't hammer a failing
|
||||||
|
// address) AND the schedule (business-hours floor / future send).
|
||||||
|
query.where('retry_count', '<', 3).andWhere(function() {
|
||||||
this.whereNull('scheduled_at').orWhere('scheduled_at', '<=', now);
|
this.whereNull('scheduled_at').orWhere('scheduled_at', '<=', now);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// A manual "send now" (ignoreSchedule) deliberately bypasses BOTH the
|
||||||
|
// schedule and the retry cap: the admin is forcing a retry, typically
|
||||||
|
// right after fixing SMTP. Without this, emails that failed 3× during
|
||||||
|
// an SMTP outage are stuck "pending" forever with no way to resend.
|
||||||
pendingEmails = await query
|
pendingEmails = await query
|
||||||
.orderBy('scheduled_at', 'asc')
|
.orderBy('scheduled_at', 'asc')
|
||||||
.orderBy('created_at', 'asc')
|
.orderBy('created_at', 'asc')
|
||||||
|
|||||||
Reference in New Issue
Block a user