← back to Sample Followup Sweep
Add fail-closed sample follow-up compliance gate
c74ebf76b7109a13ae4d8081f32d31a5d9dc0855 · 2026-08-28 11:44:03 -0700 · Steve Abrams
Files touched
M README.mdM lib/compose.jsA lib/pre-send-gate.jsM scripts/scheduled-run.mjsM scripts/send-drafts.jsM server.jsA test/pre-send-gate.test.js
Diff
commit c74ebf76b7109a13ae4d8081f32d31a5d9dc0855
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Aug 28 11:44:03 2026 -0700
Add fail-closed sample follow-up compliance gate
---
README.md | 8 ++++++++
lib/compose.js | 2 ++
lib/pre-send-gate.js | 24 ++++++++++++++++++++++++
scripts/scheduled-run.mjs | 3 +++
scripts/send-drafts.js | 13 ++++++++++---
server.js | 6 ++++++
test/pre-send-gate.test.js | 30 ++++++++++++++++++++++++++++++
7 files changed, 83 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 5720ae5..6fe84de 100644
--- a/README.md
+++ b/README.md
@@ -17,6 +17,14 @@ Canary vendor: **Designers Guild @ Osborne And Little** (O&L USA, acct 1433803).
| 5 Send | **human gate** — you batch-send from info@ Drafts | manual |
| 6 Stamp | `2nd Request = today` via `fm_update_record` dry-run→commit | needs `API_SampleMemos` layout (else push the FileMaker button) |
+## Fail-closed pre-send compliance gate
+
+Every automated draft/send path calls `lib/pre-send-gate.js` before George. It
+blocks unless the sender resolves to `info@designerwallcoverings.com`, the body
+contains DW's physical postal address and a working opt-out link, and the subject
+is present without a misleading `Re:`/`Fwd:` prefix. A block performs no network
+request and returns `PRE_SEND_COMPLIANCE_BLOCKED` (HTTP 422 in the console API).
+
## The one FileMaker dependency (for hands-free phases 1 & 6)
Create a Data-API-enabled list layout on **`WALLPAPER2`** named `API_SampleMemos`
diff --git a/lib/compose.js b/lib/compose.js
index dce694f..fff9a4a 100644
--- a/lib/compose.js
+++ b/lib/compose.js
@@ -46,6 +46,8 @@ ${esc(s.phone)}
</p>
<p>We truly appreciate the help.</p>
<p>Best Regards,<br>Showroom Manager</p>
+<p style="font-size:11px;color:#666">Designer Wallcoverings · 15442 Ventura Blvd. #102 · Sherman Oaks, CA 91403<br>
+To stop receiving sample follow-up emails, <a href="mailto:info@designerwallcoverings.com?subject=Unsubscribe%20from%20sample%20follow-ups">unsubscribe here</a>.</p>
</div>`;
return { to, subject, html };
diff --git a/lib/pre-send-gate.js b/lib/pre-send-gate.js
new file mode 100644
index 0000000..257ce45
--- /dev/null
+++ b/lib/pre-send-gate.js
@@ -0,0 +1,24 @@
+'use strict';
+
+const REQUIRED_FROM = 'info@designerwallcoverings.com';
+const ADDRESS_PATTERN = /15442\s+Ventura\s+Blvd(?:\.|\s)*(?:#|Suite)?\s*102[\s\S]*Sherman\s+Oaks,?\s*CA\s+91403/i;
+const UNSUBSCRIBE_PATTERN = /(?:mailto:|https?:\/\/)[^"'\s>]*(?:unsubscribe|opt[-_ ]?out)|(?:unsubscribe|opt\s*out)[\s\S]{0,160}(?:mailto:|https?:\/\/)/i;
+const MISLEADING_SUBJECT_PATTERN = /\b(?:re|fw|fwd)\s*:/i;
+
+function assertPreSendCompliance({ from, subject, body }) {
+ const errors = [];
+ if (String(from || '').trim().toLowerCase() !== REQUIRED_FROM) errors.push(`From must be ${REQUIRED_FROM}`);
+ if (!String(subject || '').trim()) errors.push('Subject is required');
+ if (MISLEADING_SUBJECT_PATTERN.test(String(subject || ''))) errors.push('Subject must not imply a reply or forward');
+ if (!ADDRESS_PATTERN.test(String(body || ''))) errors.push('Body must include the DW physical postal address');
+ if (!UNSUBSCRIBE_PATTERN.test(String(body || ''))) errors.push('Body must include a working unsubscribe or opt-out link');
+ if (errors.length) {
+ const error = new Error(`PRE-SEND COMPLIANCE GATE BLOCKED: ${errors.join('; ')}`);
+ error.code = 'PRE_SEND_COMPLIANCE_BLOCKED';
+ error.reasons = errors;
+ throw error;
+ }
+ return true;
+}
+
+module.exports = { assertPreSendCompliance, REQUIRED_FROM };
diff --git a/scripts/scheduled-run.mjs b/scripts/scheduled-run.mjs
index 4d4d1c3..7de4ce1 100644
--- a/scripts/scheduled-run.mjs
+++ b/scripts/scheduled-run.mjs
@@ -27,6 +27,7 @@ const require = createRequire(import.meta.url);
const __dir = dirname(fileURLToPath(import.meta.url));
const ROOT = join(__dir, '..');
const { compose } = require(join(ROOT, 'lib', 'compose.js'));
+const { assertPreSendCompliance, REQUIRED_FROM } = require(join(ROOT, 'lib', 'pre-send-gate.js'));
const SEND = process.argv.includes('--send');
// Steve 8/20: DRAFT mode — create a Gmail draft per vendor in info@ Drafts for human review,
@@ -108,6 +109,7 @@ function georgeCreds() {
return { auth: 'Basic ' + Buffer.from(auth).toString('base64'), token: g('GEORGE_EXTERNAL_SEND_TOKEN') };
}
function georgeSend(payload) {
+ assertPreSendCompliance({ from: payload.account === 'info' ? REQUIRED_FROM : '', subject: payload.subject, body: payload.body });
const { auth, token } = georgeCreds();
const body = JSON.stringify(payload);
return new Promise((resolve) => {
@@ -120,6 +122,7 @@ function georgeSend(payload) {
}
// Create a Gmail draft in info@ Drafts (POST /api/drafts — no send-approval token needed).
function georgeDraft(payload) {
+ assertPreSendCompliance({ from: payload.account === 'info' ? REQUIRED_FROM : '', subject: payload.subject, body: payload.body });
const { auth } = georgeCreds();
const body = JSON.stringify(payload);
return new Promise((resolve) => {
diff --git a/scripts/send-drafts.js b/scripts/send-drafts.js
index ecf4010..ea798c2 100644
--- a/scripts/send-drafts.js
+++ b/scripts/send-drafts.js
@@ -5,6 +5,7 @@
const fs = require('fs');
const http = require('http');
const path = require('path');
+const { assertPreSendCompliance, REQUIRED_FROM } = require('../lib/pre-send-gate');
const GENV = '/Users/macstudio3/Projects/george-gmail/.env';
function env(k) { try { const m = fs.readFileSync(GENV, 'utf8').match(new RegExp('^' + k + '=(.+)$', 'm')); return m ? m[1].trim() : ''; } catch (e) { return ''; } }
@@ -19,13 +20,19 @@ const drafts = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'out', 'all
function send(d) {
return new Promise((res) => {
- const payload = JSON.stringify({ account: 'info', to: d.to, subject: d.subject, body: d.body });
+ const payload = { account: 'info', to: d.to, subject: d.subject, body: d.body };
+ try {
+ assertPreSendCompliance({ from: payload.account === 'info' ? REQUIRED_FROM : '', subject: payload.subject, body: payload.body });
+ } catch (error) {
+ return res({ code: 0, body: JSON.stringify({ success: false, error: error.message }) });
+ }
+ const wirePayload = JSON.stringify(payload);
const req = http.request({
host: '127.0.0.1', port: 9850, path: '/api/send', method: 'POST',
- headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload), Authorization: basic, 'X-Send-Approval': token },
+ headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(wirePayload), Authorization: basic, 'X-Send-Approval': token },
}, r => { let b = ''; r.on('data', x => b += x); r.on('end', () => res({ code: r.statusCode, body: b })); });
req.on('error', e => res({ code: 0, body: e.message }));
- req.write(payload); req.end();
+ req.write(wirePayload); req.end();
});
}
diff --git a/server.js b/server.js
index 8da9faf..de69a0f 100644
--- a/server.js
+++ b/server.js
@@ -9,6 +9,7 @@ const fs = require('fs');
const path = require('path');
const { spawn } = require('child_process');
const { compose } = require('./lib/compose');
+const { assertPreSendCompliance, REQUIRED_FROM } = require('./lib/pre-send-gate');
const ROOT = __dirname;
const AUTH = 'Basic ' + Buffer.from('admin:DW2024!').toString('base64');
@@ -135,6 +136,11 @@ const server = http.createServer(async (req, res) => {
const addrs = String(payload.to).split(',').map(a => a.trim().toLowerCase()).filter(a => a.includes('@'));
if (addrs.length && addrs.every(a => be[a])) return send(res, 409, { error: 'already sent to this vendor — click again to force-resend', to: payload.to, alreadySent: true });
}
+ try {
+ assertPreSendCompliance({ from: payload.account === 'info' ? REQUIRED_FROM : '', subject: payload.subject, body: payload.body });
+ } catch (error) {
+ return send(res, 422, { error: error.message, reasons: error.reasons || [] });
+ }
// George's canonical creds live in the DW-MCP .env (the file George's server loads into `creds`);
// GEORGE_EXTERNAL_SEND_TOKEN lives in george-gmail/.env. Search both, canonical first. (2026-08-15 fix:
// george-gmail/.env has no GEORGE_BASIC_AUTH, so the old single-file read sent an empty pass → 401.)
diff --git a/test/pre-send-gate.test.js b/test/pre-send-gate.test.js
new file mode 100644
index 0000000..423093b
--- /dev/null
+++ b/test/pre-send-gate.test.js
@@ -0,0 +1,30 @@
+'use strict';
+
+const assert = require('node:assert/strict');
+const { assertPreSendCompliance, REQUIRED_FROM } = require('../lib/pre-send-gate');
+const { compose } = require('../lib/compose');
+
+const compliant = compose({
+ name: 'Test Vendor',
+ account_number: '123',
+ sample_email: 'samples@example.com',
+ ship_to: {
+ name: 'Designer Wallcoverings',
+ line1: '15442 Ventura Blvd. #102',
+ city_state_zip: 'Sherman Oaks, CA 91403',
+ phone: '1-888-373-4564',
+ },
+}, [{ sku: 'DW-1', requested: '08/01/2026', mfr: 'ABC' }]);
+
+assert.equal(assertPreSendCompliance({ from: REQUIRED_FROM, subject: compliant.subject, body: compliant.html }), true);
+
+for (const mutation of [
+ { from: 'wrong@example.com', subject: compliant.subject, body: compliant.html },
+ { from: REQUIRED_FROM, subject: `Re: ${compliant.subject}`, body: compliant.html },
+ { from: REQUIRED_FROM, subject: compliant.subject, body: compliant.html.replace(/15442 Ventura Blvd\. #102/g, '') },
+ { from: REQUIRED_FROM, subject: compliant.subject, body: compliant.html.replace(/<a href="mailto:info@designerwallcoverings\.com\?subject=Unsubscribe[\s\S]*?<\/a>/, '') },
+]) {
+ assert.throws(() => assertPreSendCompliance(mutation), { code: 'PRE_SEND_COMPLIANCE_BLOCKED' });
+}
+
+console.log('pre-send compliance gate: PASS');
← 7dd3faf auto-data-snapshot: 2026-08-28T10:53:29 (1 data files) — dat
·
back to Sample Followup Sweep
·
auto-data-snapshot: 2026-08-28T14:34:59 (1 data files) — REA c70d49a →