[object Object]

← back to Dw Install Docs

add info@ canned-reply system (responses.json + send.mjs) with vinyl-wall-install template

cbf404e398266dfa95b3ace803ed912b0b34b4a0 · 2026-07-10 12:18:45 -0700 · Steve

Files touched

Diff

commit cbf404e398266dfa95b3ace803ed912b0b34b4a0
Author: Steve <steve@designerwallcoverings.com>
Date:   Fri Jul 10 12:18:45 2026 -0700

    add info@ canned-reply system (responses.json + send.mjs) with vinyl-wall-install template
---
 README.md             | 20 +++++++++++++++++
 canned/responses.json | 16 ++++++++++++++
 canned/send.mjs       | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 96 insertions(+)

diff --git a/README.md b/README.md
index 4db4ba1..549295d 100644
--- a/README.md
+++ b/README.md
@@ -26,6 +26,26 @@ Sent through George's `/api/send-with-attachment` (info@ account). Once the
 after a session restart) Claude can attach + send these directly. Until then,
 use a small POST script with the George admin password.
 
+## Canned replies (info@)
+
+Reusable customer-reply templates live in `canned/responses.json` — each has an
+`id`, `subject`, HTML `body`, and `attachments` (paths relative to repo root).
+
+Send one (with its attachments) via George:
+
+```
+node canned/send.mjs <response-id> <to-email> [account]
+# e.g. node canned/send.mjs vinyl-wall-install customer@example.com
+```
+
+`account` defaults to `info` (info@designerwallcoverings.com) — the customer-facing
+sender. Auth + endpoint are read from the george MCP env in `~/.claude.json`, so
+there is no separate secret to manage. Add a new template = append an object to
+`responses.json` (attachments should point at files in `dist/`).
+
+Current templates:
+- `vinyl-wall-install` — vinyl / vegan-leather wall installation + spec sheets.
+
 ## Notes
 - Letterhead = shared header/footer block in each HTML (DW name + contact).
   Keep both sheets on the same template so they read as a set.
diff --git a/canned/responses.json b/canned/responses.json
new file mode 100644
index 0000000..afd1a91
--- /dev/null
+++ b/canned/responses.json
@@ -0,0 +1,16 @@
+{
+  "_comment": "Canned reply templates for info@designerwallcoverings.com. Each entry has an id, subject, HTML body, and attachment paths (relative to repo root). Send with: node canned/send.mjs <id> <to> [account]. Default account=info.",
+  "responses": [
+    {
+      "id": "vinyl-wall-install",
+      "name": "Vinyl / Vegan Leather — wall installation + spec sheets",
+      "when": "Customer wants to install an upholstery vinyl / vegan-leather / pigskin good on a WALL and asks how, or asks for specs/care/flammability.",
+      "subject": "Installing Vinyl / Vegan Leather as a Wallcovering — Instructions & Specs",
+      "body": "<p>Hello,</p><p>Thank you for reaching out to Designer Wallcoverings &amp; Fabrics. Our upholstery-grade vegan leather &amp; vinyl can be installed as a wallcovering. Attached are two sheets to guide your installer:</p><ul><li><b>Vegan Leather for Walls — Hanging Instructions</b>: wall prep, the paste-the-wall method, seams, recommended adhesives, what to avoid, and a before-you-start checklist.</li><li><b>American&trade; Sports Vinyl — Spec &amp; Care Sheet</b>: width, content, wear rating, flammability, and cleaning (Football UVL-1050 / Basketball UVL-1060).</li></ul><p>Please always confirm the recommended adhesive with the specific product and test on a sample before full installation. Let us know if you have any questions!</p><p>Best,<br>Designer Wallcoverings &amp; Fabrics<br>(888) 373-4564 &nbsp;·&nbsp; www.DesignerWallcoverings.com</p>",
+      "attachments": [
+        "dist/Vegan-Leather-Wall-HangGuide.pdf",
+        "dist/Pigskin-Vinyl-Spec-Sheet.pdf"
+      ]
+    }
+  ]
+}
diff --git a/canned/send.mjs b/canned/send.mjs
new file mode 100755
index 0000000..806c308
--- /dev/null
+++ b/canned/send.mjs
@@ -0,0 +1,60 @@
+#!/usr/bin/env node
+// Send a canned reply (with its attachments) via George's /api/send-with-attachment.
+// Usage: node canned/send.mjs <response-id> <to-email> [account]
+//   account defaults to "info" (info@designerwallcoverings.com). Only "info" or
+//   "steve-office" are supported by the George route.
+// Auth + endpoint are read from the george MCP env in ~/.claude.json (the same
+// credential the mcp__george__ tools use) — no separate secret to manage.
+import fs from "node:fs";
+import path from "node:path";
+
+const [, , id, to, accountArg] = process.argv;
+if (!id || !to) {
+  console.error("Usage: node canned/send.mjs <response-id> <to-email> [account]");
+  process.exit(1);
+}
+const account = accountArg || "info";
+const repo = path.resolve(path.dirname(new URL(import.meta.url).pathname), "..");
+
+// 1. Load the canned response
+const store = JSON.parse(fs.readFileSync(path.join(repo, "canned/responses.json"), "utf8"));
+const r = store.responses.find((x) => x.id === id);
+if (!r) {
+  console.error(`No canned response "${id}". Available: ${store.responses.map((x) => x.id).join(", ")}`);
+  process.exit(1);
+}
+
+// 2. Read George auth/endpoint from ~/.claude.json mcpServers.george.env
+const cfg = JSON.parse(fs.readFileSync(path.join(process.env.HOME, ".claude.json"), "utf8"));
+let env = null;
+(function walk(o) {
+  for (const k in o) {
+    if (k === "mcpServers" && o[k] && o[k].george) { env = o[k].george.env; return; }
+    if (o[k] && typeof o[k] === "object") walk(o[k]);
+    if (env) return;
+  }
+})(cfg);
+if (!env || !env.GEORGE_BASIC_AUTH) { console.error("No george MCP auth in ~/.claude.json"); process.exit(1); }
+const base = (env.GEORGE_URL || "https://kamatera.tail79cb8e.ts.net:9850").replace(/\/$/, "");
+
+const MIME = { ".pdf": "application/pdf", ".doc": "application/msword", ".png": "image/png", ".jpg": "image/jpeg" };
+const attachments = (r.attachments || []).map((rel) => {
+  const p = path.join(repo, rel);
+  return {
+    filename: path.basename(rel),
+    content_base64: fs.readFileSync(p).toString("base64"),
+    mime_type: MIME[path.extname(rel).toLowerCase()] || "application/octet-stream",
+  };
+});
+
+const headers = { "Content-Type": "application/json", Authorization: "Basic " + env.GEORGE_BASIC_AUTH };
+if (env.GEORGE_EXTERNAL_SEND_TOKEN) headers["X-Send-Approval"] = env.GEORGE_EXTERNAL_SEND_TOKEN;
+
+const res = await fetch(base + "/api/send-with-attachment", {
+  method: "POST",
+  headers,
+  body: JSON.stringify({ account, to, subject: r.subject, body: r.body, attachments }),
+});
+console.log("HTTP", res.status);
+console.log(await res.text());
+if (!res.ok) process.exit(1);

← 28c20bc initial: DW letterhead install/spec sheets (vegan-leather ha  ·  back to Dw Install Docs  ·  chore: build.sh nullglob guard (session close) d720554 →