From e3a121d562f03f26cb9c47e70474d8c51107e53c Mon Sep 17 00:00:00 2001 From: louispaulb Date: Mon, 15 Jun 2026 06:51:04 -0400 Subject: [PATCH] fix(inbox): never resolve our own domains to a customer (wrong sender names) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: some F customer records carry our own/staff addresses in email_billing, so matchCustomer's `email_billing LIKE '%addr%'` resolved support@targo.ca -> "Guylaine Gagnon" and gilles@targointernet.com -> "Sylvie Juteau". Every thread on one of our addresses then inherited that wrong customer + name. - OWN_DOMAINS promoted to a single source in lib/helpers.js (was duplicated in conversation.js); inbox-triage.matchCustomer() now returns null for any own-domain address — a customer is never reachable at targo.ca / targointernet.com / gigafibre.ca. - conversation.js consumes the shared OWN_DOMAINS (removes the local copy). Also ran a one-shot repair (temporary endpoint, since removed) that unlinked the 6 already-contaminated threads and reset their display name to the real address. Co-Authored-By: Claude Opus 4.8 (1M context) --- services/targo-hub/lib/conversation.js | 4 ++-- services/targo-hub/lib/helpers.js | 6 +++++- services/targo-hub/lib/inbox-triage.js | 6 +++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/services/targo-hub/lib/conversation.js b/services/targo-hub/lib/conversation.js index f824249..6b65e2e 100644 --- a/services/targo-hub/lib/conversation.js +++ b/services/targo-hub/lib/conversation.js @@ -3,7 +3,7 @@ const crypto = require('crypto') const fs = require('fs') const path = require('path') const cfg = require('./config') -const { log, json, parseBody, lookupCustomerByPhone, lookupCustomersByPhone, lookupCustomersByEmail, createCommunication, readJsonFile, writeJsonFile, stripDataUrl } = require('./helpers') +const { log, json, parseBody, lookupCustomerByPhone, lookupCustomersByPhone, lookupCustomersByEmail, createCommunication, readJsonFile, writeJsonFile, stripDataUrl, OWN_DOMAINS } = require('./helpers') const erp = require('./erp') const sse = require('./sse') @@ -259,7 +259,7 @@ const PIPELINE_STAGES = ['Nouveau', 'Qualifié', 'Devis', 'Gagné', 'Perdu'] // // ── Garde-fous anti-spam de file (ingest courriel) ──────────────────────────── // Empêche le blast d'une file pour : nos PROPRES notifs réingérées (boucle), les expéditeurs de NOS domaines // (staff/systèmes internes), et les RÉPONSES à un ticket existant (→ rattacher au fil, pas un nouveau routage). -const OWN_DOMAINS = /@([a-z0-9-]+\.)*(targo\.ca|targointernet\.com|gigafibre\.ca)$/i // inclut les SOUS-DOMAINES (ex. directadmin.targointernet.com = notre infra) → interne, jamais un « client » +// OWN_DOMAINS = source unique dans lib/helpers.js (inclut les SOUS-DOMAINES infra ; jamais un « client »). const OWN_NOTIF_SUBJECT = /^\s*(re\s*:\s*)?\[(inbox|ticket)\s*[·:]/i // nos sujets [Inbox · …] / [Ticket · …] const TICKET_REPLY_SUBJECT = /\[ticket\s*#?\s*[\w-]+\]/i // réponse à un ticket existant : [Ticket #ISS-2026-001] ou [Ticket #250819] // Expéditeur MACHINE (jamais un humain à qui répondre dans l'inbox client) : no-reply, daemon de courriel, panneau d'hébergement… diff --git a/services/targo-hub/lib/helpers.js b/services/targo-hub/lib/helpers.js index af6184e..adb4a8a 100644 --- a/services/targo-hub/lib/helpers.js +++ b/services/targo-hub/lib/helpers.js @@ -188,9 +188,13 @@ function saveSeenSet (path, set, cap = 3000) { try { fs.writeFileSync(path, JSON // Retire le préfixe data:image/...;base64, → base64 pur (pour l'OCR vision). function stripDataUrl (s) { return String(s || '').replace(/^data:[^;]+;base64,/, '') } +// NOS PROPRES domaines (incl. sous-domaines infra) — SOURCE UNIQUE. Une adresse qui matche ici n'est +// JAMAIS un client : ni à matcher comme fiche (triage), ni à utiliser comme clé de regroupement (ingest). +const OWN_DOMAINS = /@([a-z0-9-]+\.)*(targo\.ca|targointernet\.com|gigafibre\.ca)$/i + module.exports = { log, json, parseBody, httpRequest, cors, erpFetch, erpRequest, lookupCustomerByPhone, lookupCustomersByPhone, lookupCustomersByEmail, createCommunication, nbiRequest, deepGetValue, - readJsonFile, writeJsonFile, loadSeenSet, saveSeenSet, stripDataUrl, + readJsonFile, writeJsonFile, loadSeenSet, saveSeenSet, stripDataUrl, OWN_DOMAINS, } diff --git a/services/targo-hub/lib/inbox-triage.js b/services/targo-hub/lib/inbox-triage.js index 156e619..9fef029 100644 --- a/services/targo-hub/lib/inbox-triage.js +++ b/services/targo-hub/lib/inbox-triage.js @@ -2,7 +2,7 @@ // classifyEmail({from,subject,body}) : client connu ? type ? faut-il suggérer un ticket ? // SANS dépendance à la boîte mail (testable seul + réutilisable par le poller une fois la boîte branchée). const cfg = require('./config') -const { log } = require('./helpers') +const { log, OWN_DOMAINS } = require('./helpers') const erp = require('./erp') function extractEmail (from) { @@ -14,6 +14,10 @@ function extractEmail (from) { // Match client DÉTERMINISTE par courriel (email_id, puis email_billing qui peut contenir plusieurs adresses). async function matchCustomer (email) { if (!email) return null + // JAMAIS matcher une de NOS adresses (support@targo.ca, *@targointernet.com…) sur une fiche : des + // enregistrements F contiennent par erreur nos adresses dans email_billing → le LIKE liait support@targo.ca + // à « Guylaine Gagnon », gilles@ à « Sylvie Juteau ». Une fiche client n'est jamais joignable à nos domaines. + if (OWN_DOMAINS.test(email)) return null try { let r = await erp.list('Customer', { filters: [['email_id', '=', email]], fields: ['name', 'customer_name'], limit: 1 }) if (r && r[0]) return r[0]