Control centre: don't block the provisioning report on email; fix resend

- The welcome email is sent in the background, so a slow mail server no
  longer makes provision-tenants.sh time out.
- SMTP connection/greeting/socket timeouts, so a bad host fails with a
  reason in the agency log instead of hanging.
- "Resend welcome" never sent anything (report() only emails on the
  change to running); it now sends directly and shows the error if any.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
metatroncubeswdev 2026-10-25 13:15:45 -04:00
parent 86f4f2c089
commit 1e97bbd31f
2 changed files with 15 additions and 9 deletions

View File

@ -185,14 +185,20 @@ export async function report(slug, { actual, error }) {
if (actual === "running" && ["provisioning", "past_due"].includes(x.status)) x.status = "active"; if (actual === "running" && ["provisioning", "past_due"].includes(x.status)) x.status = "active";
}, error ? `provisioning error: ${String(error).slice(0, 120)}` : `container ${actual}`); }, error ? `provisioning error: ${String(error).slice(0, 120)}` : `container ${actual}`);
if (!error && actual === "running" && before.actual !== "running" && t.setupToken && !t.welcomedAt) { if (!error && actual === "running" && before.actual !== "running" && t.setupToken && !t.welcomedAt) {
// Don't make the script wait for the mail server; the outcome lands in the agency's log on Admin → Agencies.
sendWelcome(slug, t).catch(() => {});
}
return PUBLIC(read()[slug]);
}
async function sendWelcome(slug, t) {
const sent = await notify.sendEmail({ const sent = await notify.sendEmail({
to: t.ownerEmail, subject: `Your Quorla workspace is ready`, heading: `Welcome to Quorla, ${t.ownerName || t.agencyName}`, to: t.ownerEmail, subject: `Your Quorla workspace is ready`, heading: `Welcome to Quorla, ${t.ownerName || t.agencyName}`,
lines: [`Your workspace for ${t.agencyName} is ready at ${t.url}.`, "Use the button to create your admin account. The link works once; after that, sign in with your email and password.", "Then follow the Getting started list: connect your clients' accounts, invite your team and schedule your first post."], lines: [`Your workspace for ${t.agencyName} is ready at ${t.url}.`, "Use the button to create your admin account. The link works once; after that, sign in with your email and password.", "Then follow the Getting started list: connect your clients' accounts, invite your team and schedule your first post."],
button: { label: "Create my account", url: `${t.url}/?setup=${t.setupToken}` }, fromName: "Quorla", button: { label: "Create my account", url: `${t.url}/?setup=${t.setupToken}` }, fromName: "Quorla",
}); });
update(slug, (x) => { if (sent.sent) x.welcomedAt = now(); }, sent.sent ? "welcome email sent" : `welcome email not sent: ${sent.reason}`); update(slug, (x) => { if (sent.sent) x.welcomedAt = now(); }, sent.sent ? "welcome email sent" : `welcome email not sent: ${sent.reason}`);
} return sent;
return PUBLIC(read()[slug]);
} }
/** Send the welcome email again (the setup link only works until the first account exists). */ /** Send the welcome email again (the setup link only works until the first account exists). */
@ -200,8 +206,7 @@ export async function resendWelcome(slug) {
const t = read()[slug]; const t = read()[slug];
if (!t) throw err("Unknown agency.", 404); if (!t) throw err("Unknown agency.", 404);
if (t.actual !== "running") throw err("The workspace isn't running yet."); if (t.actual !== "running") throw err("The workspace isn't running yet.");
update(slug, (x) => { delete x.welcomedAt; }); const sent = await sendWelcome(slug, t);
await report(slug, { actual: "running" }); if (!sent.sent) throw err(`Welcome email not sent: ${sent.reason}`, 502);
update(slug, () => {}, "welcome email re-sent");
return PUBLIC(read()[slug]); return PUBLIC(read()[slug]);
} }

View File

@ -23,6 +23,7 @@ function mailer() {
transport ||= nodemailer.createTransport({ transport ||= nodemailer.createTransport({
host: process.env.SMTP_HOST, port: Number(process.env.SMTP_PORT || 587), secure: Number(process.env.SMTP_PORT) === 465, host: process.env.SMTP_HOST, port: Number(process.env.SMTP_PORT || 587), secure: Number(process.env.SMTP_PORT) === 465,
auth: process.env.SMTP_USER ? { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS || "" } : undefined, auth: process.env.SMTP_USER ? { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS || "" } : undefined,
connectionTimeout: 15000, greetingTimeout: 15000, socketTimeout: 30000, // fail with a reason instead of hanging for minutes
}); });
return transport; return transport;
} }