← back to Marketing Command Center
scripts/cc-import-fm-preview.js
66 lines
#!/usr/bin/env node
/*
* cc-import-fm-preview.js — STAGE (do not upload) a Constant Contact import of
* the FileMaker CLIENT list only. DTD verdict A (2026-07-17): build the gated
* import scoped to the CAN-SPAM-legit existing-relationship client list; the
* scraped prospect emails are NOT included and stay held.
*
* This ONLY reads clients-fm.json and writes a CC-ready payload + stats. It makes
* ZERO calls to Constant Contact. The actual upload is a separate Steve-gated
* step (see the pending-approval memo).
*/
const fs = require('fs');
const path = require('path');
const SRC = path.join(__dirname, '..', 'public', 'data', 'clients-fm.json');
const OUT = path.join(__dirname, '..', 'data', 'cc-import-fm-clients.json');
const LIST_NAME = 'DW FileMaker Clients (existing relationship)';
const EMAIL_RE = /^[^@\s]+@[^@\s]+\.[^@\s]{2,}$/;
const clean = s => String(s == null ? '' : s).trim();
const raw = JSON.parse(fs.readFileSync(SRC, 'utf8'));
const clients = raw.clients || [];
const seen = new Set();
const import_data = [];
let noEmail = 0, badEmail = 0, dupe = 0;
for (const c of clients) {
const email = clean(c.email).toLowerCase();
if (!email) { noEmail++; continue; }
if (!EMAIL_RE.test(email)) { badEmail++; continue; }
if (seen.has(email)) { dupe++; continue; }
seen.add(email);
const name = clean(c.name);
let first = '', last = '';
if (name) { const p = name.split(/\s+/); first = p.shift() || ''; last = p.join(' '); }
const rec = { email_address: email };
if (first) rec.first_name = first.slice(0, 50);
if (last) rec.last_name = last.slice(0, 50);
const company = clean(c.company); if (company) rec.company_name = company.slice(0, 50);
const phone = clean(c.phone); if (phone) rec.phone = phone.slice(0, 25);
import_data.push(rec);
}
const payload = {
staged_at: new Date().toISOString(),
source: 'clients-fm.json (FileMaker Clients)',
list_name: LIST_NAME,
can_spam_basis: 'existing business relationship (clients who ordered samples/products from Designer Wallcoverings)',
scraped_excluded: true,
total_fm_records: clients.length,
with_valid_unique_email: import_data.length,
dropped: { no_email: noEmail, bad_email: badEmail, duplicate: dupe },
named: import_data.filter(r => r.first_name || r.last_name).length,
with_company: import_data.filter(r => r.company_name).length,
// CC v3 shape: POST /activities/contacts_json_import { import_data, list_ids:[<newListId>] }
import_data,
};
fs.mkdirSync(path.dirname(OUT), { recursive: true });
fs.writeFileSync(OUT, JSON.stringify(payload));
console.log(`✓ staged ${path.relative(process.cwd(), OUT)} (NO upload — CC untouched)`);
console.log(` target list: "${LIST_NAME}"`);
console.log(` ${import_data.length.toLocaleString()} valid unique-email contacts of ${clients.length.toLocaleString()} FM records`);
console.log(` dropped: ${noEmail.toLocaleString()} no-email · ${badEmail.toLocaleString()} bad-email · ${dupe.toLocaleString()} duplicate`);
console.log(` ${payload.named.toLocaleString()} have a contact name · ${payload.with_company.toLocaleString()} have a company`);
console.log(` scraped prospect emails: EXCLUDED (held)`);