fix(inbox): never resolve our own domains to a customer (wrong sender names)

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) <noreply@anthropic.com>
This commit is contained in:
louispaulb 2026-06-15 06:51:04 -04:00
parent e604cbf237
commit 9466645e17
3 changed files with 12 additions and 4 deletions

View File

@ -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…

View File

@ -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,
}

View File

@ -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]