diff --git a/apps/api/src/mailer.ts b/apps/api/src/mailer.ts index 9389938..da482bb 100644 --- a/apps/api/src/mailer.ts +++ b/apps/api/src/mailer.ts @@ -118,6 +118,15 @@ class RoutingMailer implements Mailer { cfg.smtp.user && cfg.smtp.password ? { user: cfg.smtp.user, pass: cfg.smtp.password } : undefined, + // Nodemailer's default connection timeout is 2 MINUTES — longer + // than the platform gateway's ~60s, so a misconfigured host (wrong + // hostname, filtered port) surfaced as an opaque 504 instead of + // our clean ETIMEDOUT/ECONNREFUSED error, and a real send (partner + // invite, signup) could pin its request just as long. Fail fast: + // a healthy SMTP handshake completes in single-digit seconds. + connectionTimeout: 10_000, + greetingTimeout: 10_000, + socketTimeout: 30_000, }); await transporter.sendMail({ from, diff --git a/apps/portal/src/pages/admin/Settings.tsx b/apps/portal/src/pages/admin/Settings.tsx index 96fd372..c42c77a 100644 --- a/apps/portal/src/pages/admin/Settings.tsx +++ b/apps/portal/src/pages/admin/Settings.tsx @@ -882,7 +882,7 @@ function MailSection() { type="password" value={smtpPass} onChange={(e) => setSmtpPass(e.target.value)} - placeholder={hasStoredSmtpPassword ? '•••••••• (leave blank to keep)' : ''} + placeholder={hasStoredSmtpPassword ? 'Saved — leave blank to keep, type to replace' : ''} />