← back to Sample Followup Sweep
sample-followup: self-updating suppression (live info@ Sent query via George) + 7-day rolling catch-up window; drafts stay non-duplicative hands-free
2dfaa6c3e669d682bb710697a2c354a4f15d81a7 · 2026-08-20 13:30:07 -0700 · Steve Abrams
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
M scripts/scheduled-run.mjs
Diff
commit 2dfaa6c3e669d682bb710697a2c354a4f15d81a7
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Aug 20 13:30:07 2026 -0700
sample-followup: self-updating suppression (live info@ Sent query via George) + 7-day rolling catch-up window; drafts stay non-duplicative hands-free
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
scripts/scheduled-run.mjs | 25 +++++++++++++++++++++++--
1 file changed, 23 insertions(+), 2 deletions(-)
diff --git a/scripts/scheduled-run.mjs b/scripts/scheduled-run.mjs
index 4c4590b..bbdbfea 100644
--- a/scripts/scheduled-run.mjs
+++ b/scripts/scheduled-run.mjs
@@ -45,8 +45,9 @@ const MIN_AGE_DAYS = 10;
// Sat/Sun/Mon the job didn't run (schedule = Tue–Fri). Tuesday → 4-day band [today-13..today-10]
// (memos that turned 10 on Sat/Sun/Mon/Tue); every other run day → 1 day (entered today-10).
// Env CATCHUP overrides for a manual catch-up.
-const catchupDays = (d) => (d.getDay() === 2 ? 4 : 1); // getDay: 0=Sun … 2=Tue
-const CATCHUP_DAYS = Number(process.env.CATCHUP) || catchupDays(new Date());
+// Rolling 7-day catch-up band by default so a missed/unloaded morning is always caught up next run
+// (the draft-ledger dedup + Sent-based suppression prevent any duplicate drafts). Env CATCHUP overrides.
+const CATCHUP_DAYS = Number(process.env.CATCHUP) || 7;
const SHIP_TO = { name: 'Designer Wallcoverings', line1: '15442 Ventura Blvd. #102', city_state_zip: 'Sherman Oaks, CA 91403', phone: '1-888-373-4564' };
// --- FileMaker client (reuse the connector; creds from ~/.claude.json) ---
@@ -126,6 +127,25 @@ function georgeDraft(payload) {
req.write(body); req.end();
});
}
+// Live suppression source: recipients the team already emailed a "New Sample Request" to recently
+// (harvested straight from info@ Sent via George). Self-updating — no manual list to maintain.
+function georgeRecentRecipients(days = 14) {
+ const { auth } = georgeCreds();
+ const q = encodeURIComponent(`in:sent subject:"New Sample Request" newer_than:${days}d`);
+ return new Promise((resolve) => {
+ const req = http.request({ host: '127.0.0.1', port: 9850, path: `/api/messages?account=info&maxResults=100&q=${q}`, method: 'GET',
+ headers: { Authorization: auth } },
+ (r) => { let b = ''; r.on('data', (d) => b += d); r.on('end', () => {
+ try {
+ const j = JSON.parse(b); const set = new Set();
+ for (const m of (j.messages || [])) for (const e of String(m.to || '').match(/[\w.+-]+@[\w.-]+\.\w+/g) || []) set.add(e.toLowerCase());
+ resolve(set);
+ } catch { resolve(new Set()); }
+ }); });
+ req.on('error', () => resolve(new Set()));
+ req.end();
+ });
+}
const run = async () => {
const win = windowRange();
@@ -161,6 +181,7 @@ const run = async () => {
// for (harvested from info@ Sent into data/recently-contacted.json). Stops the sweep from
// duplicating what the team already sent manually — the whole point of the 2026-08-20 fix.
const RECENT = new Set(readJSON('data/recently-contacted.json', []).map((e) => String(e).toLowerCase().trim()).filter(Boolean));
+ try { const live = await georgeRecentRecipients(14); for (const e of live) RECENT.add(e); if (live.size) console.log(`(auto-suppression: ${live.size} recipient(s) already emailed in last 14d, from info@ Sent)`); } catch {}
const sent = [], skipped = [], needsConfirm = [], drafted = [], suppressed = [];
for (const [vid, rows] of Object.entries(byVid)) {
const c = cmap[vid];
← 405e7de watch-sent-stamp: fix Sent search (drop em-dash from subject
·
back to Sample Followup Sweep
·
auto-data-snapshot: 2026-08-20T13:34:43 (1 data files) — dat b6a575a →