From c0b220836afae97df7263e97c11c88da30a96219 Mon Sep 17 00:00:00 2001 From: ScreenTinker Date: Sat, 30 May 2026 16:16:33 -0500 Subject: [PATCH] fix(signup): make admin-notify recipient env-driven, not hardcoded The admin signup-notify recipient was hardcoded to support@screentinker.com and shipped in the open-source code. Combined with the opt-out SELF_HOSTED gate, any self-hoster who configured their own Graph credentials but forgot SELF_HOSTED=true would fire their users' signup PII (email, IP, country) into our support inbox. Source the recipient from ADMIN_NOTIFY_EMAIL instead, defaulting to null. When unset, the admin notification is skipped entirely and logged ("[SIGNUP-EMAIL] admin notify skipped (ADMIN_NOTIFY_EMAIL unset)"); the user's welcome email is unaffected. Hosted prod sets the env var so its notifications continue; self-hosters send nothing to us by default, and the .com address no longer ships in code. Document ADMIN_NOTIFY_EMAIL (and the related mail/self-host vars) in a new .env.example. Co-Authored-By: Claude Opus 4.8 (1M context) --- server/services/signupEmails.js | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/server/services/signupEmails.js b/server/services/signupEmails.js index 36ce48a..b157912 100644 --- a/server/services/signupEmails.js +++ b/server/services/signupEmails.js @@ -23,7 +23,12 @@ const { sendEmail } = require('./email'); const { getClientIp } = require('./activity'); const config = require('../config'); -const ADMIN_NOTIFY_TO = 'support@screentinker.com'; +// Admin signup-notify recipient. Sourced from env (not hardcoded) so the +// hosted .com address never ships in open-source code: a self-hoster who +// configures Graph but forgets SELF_HOSTED=true would otherwise fire their +// users' signup PII into our inbox. Unset -> admin notify is skipped entirely +// (the user's welcome email is unaffected). Hosted prod sets this env var. +const ADMIN_NOTIFY_TO = process.env.ADMIN_NOTIFY_EMAIL || null; const LINKS = { player: 'https://screentinker.com/player/', @@ -155,13 +160,17 @@ function sendSignupEmails(user, req) { }); console.log(`[SIGNUP-EMAIL] welcome -> ${email}: ${JSON.stringify(w)}`); - const a = await sendEmail({ - to: ADMIN_NOTIFY_TO, - rawSubject: true, - subject: `New signup: ${email}`, - text: adminText({ name, email, orgName, signupUnix, ip, country, userAgent }), - }); - console.log(`[SIGNUP-EMAIL] admin-notify (${email}) -> ${ADMIN_NOTIFY_TO}: ${JSON.stringify(a)}`); + if (ADMIN_NOTIFY_TO) { + const a = await sendEmail({ + to: ADMIN_NOTIFY_TO, + rawSubject: true, + subject: `New signup: ${email}`, + text: adminText({ name, email, orgName, signupUnix, ip, country, userAgent }), + }); + console.log(`[SIGNUP-EMAIL] admin-notify (${email}) -> ${ADMIN_NOTIFY_TO}: ${JSON.stringify(a)}`); + } else { + console.log('[SIGNUP-EMAIL] admin notify skipped (ADMIN_NOTIFY_EMAIL unset)'); + } // Stamp after the send block regardless of per-email outcome (no retry): // marks this user handled so we never double-send.