← 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)`);