From 9b60e6414efbb3052760616febc5534404d08ea7 Mon Sep 17 00:00:00 2001 From: martbost Date: Sat, 12 Sep 2026 10:37:45 -0500 Subject: [PATCH] Referral purchase email names the buyer: @username in subject and body memberLabel() resolves @username, or the owner's username for a linked position, else member #id. Subject: '@name just bought a $20 ad package'. Co-Authored-By: Claude Fable 5.1 --- accounts.js | 3 +++ server.js | 17 +++++++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/accounts.js b/accounts.js index d2db2f6..ff2626a 100644 --- a/accounts.js +++ b/accounts.js @@ -194,6 +194,7 @@ const J = { }, async setPositionMember(a, id) { const p = this.db.positions[a]; if (p && p.memberId !== id) { p.memberId = id; this.save(); } }, async positionOwner(a) { const p = this.db.positions[a]; return p ? { address: a, email: p.email, memberId: p.memberId || 0 } : null; }, + async positionByMember(id) { const e = Object.entries(this.db.positions).find(([, p]) => p.memberId === Number(id)); return e ? { address: e[0], email: e[1].email, memberId: e[1].memberId } : null; }, async removePosition(e, a) { const p = this.db.positions[a]; if (!p || p.email !== e) return { error: 'No such position.' }; @@ -342,6 +343,7 @@ const D = { const r = (await db.q('SELECT * FROM positions WHERE address=?', [a]))[0]; return r ? { address: r.address, email: r.email, memberId: r.member_id || 0 } : null; }, + async positionByMember(id) { const r = await db.q('SELECT address, email, member_id FROM positions WHERE member_id=? LIMIT 1', [Number(id)]); return r[0] ? { address: r[0].address, email: r[0].email, memberId: r[0].member_id } : null; }, async removePosition(e, a) { const r = (await db.q('SELECT * FROM positions WHERE address=? AND email=?', [a, e]))[0]; if (!r) return { error: 'No such position.' }; @@ -452,5 +454,6 @@ module.exports = { init, signup, login, ensure, byEmail, byAddress, byCode, byUs addPosition: (e, a) => { const x = normAddr(a); return /^0x[0-9a-f]{40}$/.test(x) ? impl().addPosition(normEmail(e), x) : Promise.resolve({ error: 'Bad wallet address.' }); }, setPositionMember: (a, id) => impl().setPositionMember(normAddr(a), Number(id) || 0), positionOwner: a => impl().positionOwner(normAddr(a)), + positionByMember: id => impl().positionByMember(Number(id) || 0), removePosition: (e, a) => impl().removePosition(normEmail(e), normAddr(a)), byMemberId: id => impl().byMemberId(id) }; diff --git a/server.js b/server.js index de0e8cc..476e8fb 100644 --- a/server.js +++ b/server.js @@ -472,6 +472,16 @@ async function notifyNewReferral(ref, newAcct) { } catch (e) {} } // welcome email on a new account: onboarding steps + who their sponsor is +// how a member is named in emails/alerts: @username, a linked position named after its owner, else member #id +async function memberLabel(id) { + try { + const a = await accounts.byMemberId(id); + if (a && a.username) return '@' + a.username; + const pos = await accounts.positionByMember(id); + if (pos && pos.email) { const o = await accounts.byEmail(pos.email); if (o && o.username) return '@' + o.username + ' (extra position #' + id + ')'; } + } catch (e) {} + return 'member #' + id; +} async function sendWelcome(email, ref) { try { if (!mailer.hasKey()) return; @@ -514,14 +524,13 @@ async function emailOnEvent(ev) { if (buyer && buyer.sponsorId) { const sp = await accounts.byMemberId(buyer.sponsorId); if (sp && sp.email) { - const ba = await accounts.byMemberId(ev.buyerId); - const bn = ba && ba.username ? '@' + ba.username : 'One of your referrals'; + const bn = await memberLabel(ev.buyerId); // @username at a glance (Marty, 2026-09-12) const usd = ('$' + (ev.priceCents / 100).toFixed(2)).replace(/\.00$/, ''); const qual = ev.priceCents >= 2000 ? ' This is a $20+ purchase, so it counts toward your qualification.' : ' (Purchases under $20 do not count toward qualification.)'; - mailer.send(sp.email, 'Your referral just bought an ad package', - bn + ' just purchased a package (' + usd + ' — ' + ev.creditAmount + ' credits).' + qual + '\n\n' + + mailer.send(sp.email, bn + ' just bought a ' + usd + ' ad package', + 'Your referral ' + bn + ' (member #' + ev.buyerId + ') just purchased a package (' + usd + ' — ' + ev.creditAmount + ' credits).' + qual + '\n\n' + 'See your team and the live ledger: https://instantadpay.com/my\n\nInstantAdPay').catch(() => {}); } }