Member updates: audience defaults to newsletter opt-ins (Sendy list status), explicit 'everyone' option; sendy.status()
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
+12
-4
@@ -4,21 +4,29 @@
|
||||
// Log: DATA_DIR/updates-log.json. Nothing here sends on its own: every send is a click in Admin > Releases.
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let R = null; // { dataDir, accounts, releases, mailer, drip, adminEmail }
|
||||
let R = null; // { dataDir, accounts, releases, mailer, drip, sendy, adminEmail }
|
||||
const SITE = 'https://instantadpay.com';
|
||||
const FILE = () => path.join(R.dataDir, 'updates-log.json');
|
||||
function log() { try { return JSON.parse(fs.readFileSync(FILE(), 'utf8')); } catch (e) { return { sends: [] }; } }
|
||||
function saveLog(l) { try { fs.writeFileSync(FILE(), JSON.stringify(l)); } catch (e) {} }
|
||||
function init(refs) { R = refs; }
|
||||
|
||||
const AUDIENCES = { active30: 'Active in the last 30 days', active90: 'Active in the last 90 days', all: 'Every member' };
|
||||
const AUDIENCES = { optin: 'Newsletter opt-ins (ticked the box at sign-up)', optin30: 'Newsletter opt-ins active in the last 30 days', all: 'Every member, including those who declined the newsletter' };
|
||||
// the sign-up checkbox subscribes the member to the Sendy newsletter list, so Sendy is the record of who opted in
|
||||
const optinCache = new Map(); // email -> { t, v }
|
||||
async function optedIn(email) {
|
||||
const c = optinCache.get(email); if (c && Date.now() - c.t < 6 * 3600000) return c.v;
|
||||
const st = R.sendy ? await R.sendy.status(email) : ''; const v = st === 'Subscribed';
|
||||
optinCache.set(email, { t: Date.now(), v }); return v;
|
||||
}
|
||||
async function recipients(kind) {
|
||||
const all = await R.accounts.listAll(20000);
|
||||
const now = Date.now(); const days = kind === 'active30' ? 30 : kind === 'active90' ? 90 : 0;
|
||||
const now = Date.now(); const days = kind === 'optin30' ? 30 : 0; const needOptin = kind !== 'all';
|
||||
const out = [];
|
||||
for (const a of all) {
|
||||
if (!a.email || /@(demo|example)\./i.test(a.email)) continue;
|
||||
if (days && now - Math.max(a.lastSeen || 0, a.created || 0) > days * 86400000) continue;
|
||||
if (needOptin && !(await optedIn(a.email))) continue;
|
||||
out.push(a);
|
||||
}
|
||||
return out;
|
||||
@@ -50,7 +58,7 @@ async function send(input, opts) {
|
||||
return { ok: true, test: true, to };
|
||||
}
|
||||
if (running) return { error: 'A send is already running (' + running.sent + ' of ' + running.total + '). Wait for it to finish.' };
|
||||
const kind = AUDIENCES[input.audience] ? input.audience : 'active30';
|
||||
const kind = AUDIENCES[input.audience] ? input.audience : 'optin';
|
||||
const list = await recipients(kind);
|
||||
if (!list.length) return { error: 'Nobody in that audience.' };
|
||||
const rec = { id: Date.now().toString(36), ts: Date.now(), subject: compose(input, null).subject, noteIds, audience: kind, total: list.length, sent: 0, skipped: 0, failed: 0, status: 'running' };
|
||||
|
||||
Reference in New Issue
Block a user