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 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-12 10:37:45 -05:00
parent ebac2fcb32
commit 9b60e6414e
2 changed files with 16 additions and 4 deletions
+3
View File
@@ -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 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 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) { async removePosition(e, a) {
const p = this.db.positions[a]; const p = this.db.positions[a];
if (!p || p.email !== e) return { error: 'No such position.' }; 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]; 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; 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) { async removePosition(e, a) {
const r = (await db.q('SELECT * FROM positions WHERE address=? AND email=?', [a, e]))[0]; const r = (await db.q('SELECT * FROM positions WHERE address=? AND email=?', [a, e]))[0];
if (!r) return { error: 'No such position.' }; 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.' }); }, 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), setPositionMember: (a, id) => impl().setPositionMember(normAddr(a), Number(id) || 0),
positionOwner: a => impl().positionOwner(normAddr(a)), positionOwner: a => impl().positionOwner(normAddr(a)),
positionByMember: id => impl().positionByMember(Number(id) || 0),
removePosition: (e, a) => impl().removePosition(normEmail(e), normAddr(a)), removePosition: (e, a) => impl().removePosition(normEmail(e), normAddr(a)),
byMemberId: id => impl().byMemberId(id) }; byMemberId: id => impl().byMemberId(id) };
+13 -4
View File
@@ -472,6 +472,16 @@ async function notifyNewReferral(ref, newAcct) {
} catch (e) {} } catch (e) {}
} }
// welcome email on a new account: onboarding steps + who their sponsor is // 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) { async function sendWelcome(email, ref) {
try { try {
if (!mailer.hasKey()) return; if (!mailer.hasKey()) return;
@@ -514,14 +524,13 @@ async function emailOnEvent(ev) {
if (buyer && buyer.sponsorId) { if (buyer && buyer.sponsorId) {
const sp = await accounts.byMemberId(buyer.sponsorId); const sp = await accounts.byMemberId(buyer.sponsorId);
if (sp && sp.email) { if (sp && sp.email) {
const ba = await accounts.byMemberId(ev.buyerId); const bn = await memberLabel(ev.buyerId); // @username at a glance (Marty, 2026-09-12)
const bn = ba && ba.username ? '@' + ba.username : 'One of your referrals';
const usd = ('$' + (ev.priceCents / 100).toFixed(2)).replace(/\.00$/, ''); const usd = ('$' + (ev.priceCents / 100).toFixed(2)).replace(/\.00$/, '');
const qual = ev.priceCents >= 2000 const qual = ev.priceCents >= 2000
? ' This is a $20+ purchase, so it counts toward your qualification.' ? ' This is a $20+ purchase, so it counts toward your qualification.'
: ' (Purchases under $20 do not count toward qualification.)'; : ' (Purchases under $20 do not count toward qualification.)';
mailer.send(sp.email, 'Your referral just bought an ad package', mailer.send(sp.email, bn + ' just bought a ' + usd + ' ad package',
bn + ' just purchased a package (' + usd + ' — ' + ev.creditAmount + ' credits).' + qual + '\n\n' + '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(() => {}); 'See your team and the live ledger: https://instantadpay.com/my\n\nInstantAdPay').catch(() => {});
} }
} }