[object Object]

← back to Pitch Guard

Fix recent-followup detection — broad regex for all followup templates + body fallback when snippet misses

0bd7aef388e86bbd7ed29e1784440ea33fc6c211 · 2026-05-19 09:28:07 -0700 · Steve

Files touched

Diff

commit 0bd7aef388e86bbd7ed29e1784440ea33fc6c211
Author: Steve <steve@designerwallcoverings.com>
Date:   Tue May 19 09:28:07 2026 -0700

    Fix recent-followup detection — broad regex for all followup templates + body fallback when snippet misses
---
 server.js | 30 +++++++++++++++++++++---------
 1 file changed, 21 insertions(+), 9 deletions(-)

diff --git a/server.js b/server.js
index c88c1fc..0088a3f 100644
--- a/server.js
+++ b/server.js
@@ -185,18 +185,30 @@ app.get('/api/contact', async (req, res) => {
     if (threads.length > 1) level = Math.max(level, 1);
     const risk = level >= 3 ? 'high' : level >= 2 ? 'elevated' : level >= 1 ? 'caution' : 'clear';
 
-    // Did we already send this contact a standard followup in the last 7 days?
-    // (in:sent only — excludes the unsent draft so a queued draft doesn't count)
+    // Did we already send this contact a followup recently? The followup bot
+    // uses several phrasings, and Gmail snippets can show quoted text instead
+    // of the opening line — so match broadly and body-check when the snippet misses.
+    // (in:sent only — an unsent draft doesn't count.)
     let recentFollowup = null;
     try {
-      const FOLLOWUP_RE = /following up on the (quote|pricing)|wanted to check in and see if you/i;
-      const fu = await george('/api/search', { query: { account: ACCOUNT, q: `in:sent to:${email} newer_than:7d`, maxResults: 10 } });
-      for (const m of (fu.messages || [])) {
-        if (m.error) continue;
-        if (!FOLLOWUP_RE.test(`${m.subject || ''} ${m.snippet || ''}`)) continue;
-        const ts = Date.parse(m.date) || 0;
-        if (!recentFollowup || ts > recentFollowup.ts) {
+      const FU_RE = /follow[\s-]?up|following up|check(ing)? in|circl(e|ing) back|haven'?t heard|gentle (nudge|reminder)|chase up|did(n'?t| not) (you )?see/i;
+      const fu = await george('/api/search', { query: { account: ACCOUNT, q: `in:sent to:${email} newer_than:21d`, maxResults: 20 } });
+      const cand = (fu.messages || []).filter(m => !m.error)
+        .sort((a, b) => (Date.parse(b.date) || 0) - (Date.parse(a.date) || 0));
+      let bodyChecks = 0;
+      for (const m of cand) {
+        let isFu = FU_RE.test(`${m.subject || ''} ${m.snippet || ''}`);
+        if (!isFu && bodyChecks < 6) {            // snippet missed it — confirm via the full body
+          bodyChecks++;
+          try {
+            const full = await george('/api/messages/' + encodeURIComponent(m.id), { query: { account: ACCOUNT } });
+            isFu = FU_RE.test(full.body || '');
+          } catch (e) {}
+        }
+        if (isFu) {
+          const ts = Date.parse(m.date) || 0;
           recentFollowup = { ts, date: m.date, daysAgo: Math.max(0, Math.floor((Date.now() - ts) / 86400000)) };
+          break;                                  // newest-first → first match is the most recent
         }
       }
     } catch (e) { /* non-fatal — followup check is advisory */ }

← e7305dd Add Delete draft button to the Pitch panel footer  ·  back to Pitch Guard  ·  Add 5-day followup candidate scanner (dry-run) — Followup Ca 164394a →