From 42546c9a63542aba3a8bdb7aa636068c5f44dc38 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Fri, 3 Apr 2026 15:12:42 -0700 Subject: [PATCH] fix: use internal mailcow relay (port 25) for all SMTP transports SMTP auth on port 587 was broken across all modules due to stale credentials. Since rspace is on the mailcow Docker network, all 6 SMTP transports now use unauthenticated relay on port 25 when the host is the internal postfix container. Fixes emails for: payment receipts, space invitations, inbox approvals, agent notifications, scheduled emails, and publication sharing. Co-Authored-By: Claude Opus 4.6 --- modules/rinbox/agent-notify.ts | 8 +++++--- modules/rinbox/mod.ts | 10 ++++++---- modules/rpubs/mod.ts | 20 ++++++++++++-------- modules/rschedule/mod.ts | 20 ++++++++++++-------- server/notification-service.ts | 9 +++++---- server/spaces.ts | 28 +++++++++++++++++----------- 6 files changed, 57 insertions(+), 38 deletions(-) diff --git a/modules/rinbox/agent-notify.ts b/modules/rinbox/agent-notify.ts index dc574af..97c2b99 100644 --- a/modules/rinbox/agent-notify.ts +++ b/modules/rinbox/agent-notify.ts @@ -18,11 +18,13 @@ async function getSmtpTransport() { try { const nodemailer = await import("nodemailer"); const createTransport = (nodemailer as any).default?.createTransport || nodemailer.createTransport; + const isInternal = SMTP_HOST.includes('mailcow') || SMTP_HOST.includes('postfix'); _transport = createTransport({ host: SMTP_HOST, - port: SMTP_PORT, - secure: SMTP_PORT === 465, - auth: SMTP_USER ? { user: SMTP_USER, pass: SMTP_PASS } : undefined, + port: isInternal ? 25 : SMTP_PORT, + secure: !isInternal && SMTP_PORT === 465, + ...(isInternal ? {} : SMTP_USER ? { auth: { user: SMTP_USER, pass: SMTP_PASS } } : {}), + tls: { rejectUnauthorized: false }, }); return _transport; } catch (e) { diff --git a/modules/rinbox/mod.ts b/modules/rinbox/mod.ts index ebf8751..c43729a 100644 --- a/modules/rinbox/mod.ts +++ b/modules/rinbox/mod.ts @@ -50,13 +50,15 @@ async function getSmtpTransport() { try { const nodemailer = await import("nodemailer"); const createTransport = (nodemailer as any).default?.createTransport || nodemailer.createTransport; + const isInternal = SMTP_HOST.includes('mailcow') || SMTP_HOST.includes('postfix'); _smtpTransport = createTransport({ host: SMTP_HOST, - port: SMTP_PORT, - secure: SMTP_PORT === 465, - auth: SMTP_USER ? { user: SMTP_USER, pass: SMTP_PASS } : undefined, + port: isInternal ? 25 : SMTP_PORT, + secure: !isInternal && SMTP_PORT === 465, + ...(isInternal ? {} : SMTP_USER ? { auth: { user: SMTP_USER, pass: SMTP_PASS } } : {}), + tls: { rejectUnauthorized: false }, }); - console.log(`[Inbox] SMTP transport configured: ${SMTP_HOST}:${SMTP_PORT}`); + console.log(`[Inbox] SMTP transport configured: ${SMTP_HOST}:${isInternal ? 25 : SMTP_PORT}`); return _smtpTransport; } catch (e) { console.error("[Inbox] Failed to create SMTP transport:", e); diff --git a/modules/rpubs/mod.ts b/modules/rpubs/mod.ts index 117287d..f00627d 100644 --- a/modules/rpubs/mod.ts +++ b/modules/rpubs/mod.ts @@ -29,15 +29,19 @@ let _smtpTransport: Transporter | null = null; function getSmtpTransport(): Transporter | null { if (_smtpTransport) return _smtpTransport; - if (!process.env.SMTP_PASS) return null; + const host = process.env.SMTP_HOST || "mail.rmail.online"; + const isInternal = host.includes('mailcow') || host.includes('postfix'); + if (!process.env.SMTP_PASS && !isInternal) return null; _smtpTransport = createTransport({ - host: process.env.SMTP_HOST || "mail.rmail.online", - port: Number(process.env.SMTP_PORT) || 587, - secure: Number(process.env.SMTP_PORT) === 465, - auth: { - user: process.env.SMTP_USER || "noreply@rmail.online", - pass: process.env.SMTP_PASS, - }, + host, + port: isInternal ? 25 : (Number(process.env.SMTP_PORT) || 587), + secure: !isInternal && Number(process.env.SMTP_PORT) === 465, + ...(isInternal ? {} : { + auth: { + user: process.env.SMTP_USER || "noreply@rmail.online", + pass: process.env.SMTP_PASS!, + }, + }), tls: { rejectUnauthorized: false }, }); return _smtpTransport; diff --git a/modules/rschedule/mod.ts b/modules/rschedule/mod.ts index c0bd8cf..45ceb48 100644 --- a/modules/rschedule/mod.ts +++ b/modules/rschedule/mod.ts @@ -52,15 +52,19 @@ let _smtpTransport: Transporter | null = null; function getSmtpTransport(): Transporter | null { if (_smtpTransport) return _smtpTransport; - if (!process.env.SMTP_PASS) return null; + const host = process.env.SMTP_HOST || "mail.rmail.online"; + const isInternal = host.includes('mailcow') || host.includes('postfix'); + if (!process.env.SMTP_PASS && !isInternal) return null; _smtpTransport = createTransport({ - host: process.env.SMTP_HOST || "mail.rmail.online", - port: Number(process.env.SMTP_PORT) || 587, - secure: Number(process.env.SMTP_PORT) === 465, - auth: { - user: process.env.SMTP_USER || "noreply@rmail.online", - pass: process.env.SMTP_PASS, - }, + host, + port: isInternal ? 25 : (Number(process.env.SMTP_PORT) || 587), + secure: !isInternal && Number(process.env.SMTP_PORT) === 465, + ...(isInternal ? {} : { + auth: { + user: process.env.SMTP_USER || "noreply@rmail.online", + pass: process.env.SMTP_PASS!, + }, + }), tls: { rejectUnauthorized: false }, }); return _smtpTransport; diff --git a/server/notification-service.ts b/server/notification-service.ts index bf901a7..b9b7a77 100644 --- a/server/notification-service.ts +++ b/server/notification-service.ts @@ -43,15 +43,16 @@ let _smtpTransport: any = null; async function getSmtpTransport() { if (_smtpTransport) return _smtpTransport; - if (!SMTP_PASS) return null; + const isInternal = SMTP_HOST.includes('mailcow') || SMTP_HOST.includes('postfix'); + if (!SMTP_PASS && !isInternal) return null; try { const nodemailer = await import("nodemailer"); const createTransport = (nodemailer as any).default?.createTransport || nodemailer.createTransport; _smtpTransport = createTransport({ host: SMTP_HOST, - port: SMTP_PORT, - secure: SMTP_PORT === 465, - auth: SMTP_USER ? { user: SMTP_USER, pass: SMTP_PASS } : undefined, + port: isInternal ? 25 : SMTP_PORT, + secure: !isInternal && SMTP_PORT === 465, + ...(isInternal ? {} : SMTP_USER ? { auth: { user: SMTP_USER, pass: SMTP_PASS } } : {}), tls: { rejectUnauthorized: false }, }); console.log("[email] SMTP transport configured"); diff --git a/server/spaces.ts b/server/spaces.ts index 1970a9e..17c8b6e 100644 --- a/server/spaces.ts +++ b/server/spaces.ts @@ -2097,17 +2097,23 @@ spaces.post("/:slug/copy-shapes", async (c) => { let inviteTransport: Transporter | null = null; -if (process.env.SMTP_PASS) { - inviteTransport = createTransport({ - host: process.env.SMTP_HOST || "mail.rmail.online", - port: Number(process.env.SMTP_PORT) || 587, - secure: Number(process.env.SMTP_PORT) === 465, - auth: { - user: process.env.SMTP_USER || "noreply@rmail.online", - pass: process.env.SMTP_PASS, - }, - tls: { rejectUnauthorized: false }, - }); +{ + const host = process.env.SMTP_HOST || "mail.rmail.online"; + const isInternal = host.includes('mailcow') || host.includes('postfix'); + if (process.env.SMTP_PASS || isInternal) { + inviteTransport = createTransport({ + host, + port: isInternal ? 25 : (Number(process.env.SMTP_PORT) || 587), + secure: !isInternal && Number(process.env.SMTP_PORT) === 465, + ...(isInternal ? {} : { + auth: { + user: process.env.SMTP_USER || "noreply@rmail.online", + pass: process.env.SMTP_PASS!, + }, + }), + tls: { rejectUnauthorized: false }, + }); + } } // ── Enhanced invite by email (with token + role) ──