← back to Designer Wallcoverings
mailers/send-test-via-george.js
100 lines
#!/usr/bin/env node
/*
* send-test-via-george.js — send a CLEAN test preview of a mailer to a single
* inbox via the LIVE LOCAL George (http://127.0.0.1:9850, basic-auth admin:"").
*
* WHY THIS EXISTS
* ---------------
* The Constant Contact source mailer (e.g. china-seas-launch.html) carries CC
* footer tokens — {{ unsubscribe }} and {{update_preferences_url}} — that CC's
* own compliance footer handles on a real campaign send. George is a plain
* Gmail relay with NO merge engine, so on a self-test those tokens render
* literally and the preview looks broken.
*
* This helper substitutes those tokens with working fallback links AT SEND TIME
* ONLY. It NEVER writes the source file, so the CC-bound HTML stays byte-for-byte
* identical and the real Constant Contact campaign is completely unaffected.
*
* USAGE
* node send-test-via-george.js <mailer.html> [--to addr] [--subject "..."] \
* [--account steve-office] [--dry]
*
* Defaults: to=steve@designerwallcoverings.com, account=steve-office,
* subject derived from the file's <title>.
*/
const fs = require('fs');
const path = require('path');
// ---- fallback links used ONLY in the test preview (cosmetic; CC merges the real ones) ----
const FALLBACKS = {
// CAN-SPAM-reasonable: a real mailto so the test link is functional, not dead.
'{{ unsubscribe }}': 'mailto:steve@designerwallcoverings.com?subject=Unsubscribe',
'{{unsubscribe}}': 'mailto:steve@designerwallcoverings.com?subject=Unsubscribe',
'{{update_preferences_url}}': 'https://designerwallcoverings.com',
'{{ update_preferences_url }}': 'https://designerwallcoverings.com',
};
function parseArgs(argv) {
const a = { _: [] };
for (let i = 2; i < argv.length; i++) {
const t = argv[i];
if (t === '--dry') a.dry = true;
else if (t === '--to') a.to = argv[++i];
else if (t === '--subject') a.subject = argv[++i];
else if (t === '--account') a.account = argv[++i];
else a._.push(t);
}
return a;
}
function applyFallbacks(html) {
let out = html, n = 0;
for (const [tok, url] of Object.entries(FALLBACKS)) {
const parts = out.split(tok);
n += parts.length - 1;
out = parts.join(url);
}
return { html: out, replaced: n };
}
function deriveSubject(html) {
const m = html.match(/<title>([\s\S]*?)<\/title>/i);
return m ? `[TEST] ${m[1].trim()}` : '[TEST] Mailer preview';
}
(async () => {
const args = parseArgs(process.argv);
const file = args._[0];
if (!file) { console.error('usage: node send-test-via-george.js <mailer.html> [--to] [--subject] [--account] [--dry]'); process.exit(1); }
const abs = path.resolve(file);
const raw = fs.readFileSync(abs, 'utf8');
const { html, replaced } = applyFallbacks(raw);
const to = args.to || 'steve@designerwallcoverings.com';
const account = args.account || 'steve-office';
const subject = args.subject || deriveSubject(raw);
console.log(`mailer: ${abs} (${raw.length} bytes)`);
console.log(`tokens: ${replaced} CC token(s) swapped for test fallbacks (source file untouched)`);
console.log(`to: ${to}`);
console.log(`account: ${account}`);
console.log(`subject: ${subject}`);
if (args.dry) { console.log('\n--dry: not sending.'); return; }
const auth = 'Basic ' + Buffer.from('admin:').toString('base64');
const res = await fetch('http://127.0.0.1:9850/api/send', {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: auth },
body: JSON.stringify({ to, subject, body: html, account }),
});
const json = await res.json().catch(() => ({}));
if (!res.ok || !json.success) {
console.error(`\nSEND FAILED (${res.status}):`, JSON.stringify(json));
process.exit(1);
}
console.log(`\n✅ sent — messageId ${json.messageId} (thread ${json.threadId})`);
})().catch((e) => { console.error('FATAL', e.message); process.exit(1); });