# Recherche — Boîtes mail o2switch & routage Zammad

> Objectif : définir les adresses de réception (boîtes o2switch) et leur mapping vers les
> groupes/équipes Zammad, pour le système de routage des emails de devis.

## Contraintes o2switch (cPanel) — déterminantes

- **Nombre de boîtes/alias illimité**, sans coût additionnel ; quota de stockage paramétrable par boîte.
- **Pas de catch-all** : o2switch a retiré l'adresse par défaut (anti-spam) → tout mail vers
  une adresse inexistante **bounce**. → prévoir une boîte `contact@` comme filet.
- **Les alias/redirections ne se pollent pas** (aucun stockage) → il faut de **vraies boîtes IMAP**.
- Envoi SMTP : ~**30 msg/min**, **20 000/jour** (large pour le POC).
- Serveur (ce projet, domaine **vivacoda.com**) : **`mail.vivacoda.com`**
  — **IMAP 993 SSL** · POP3 995 · **SMTP 465 SSL/TLS** · authentification requise (user =
  adresse complète, mot de passe = celui de la boîte). À saisir dans **l'UI Zammad**, pas dans `.env`.

Sources : faq.o2switch.fr (plusieurs comptes, quota, adresse défaut, SMTP sortant, config client).

## Pattern de routage Zammad (doc admin)

- **1 compte Email Zammad = 1 boîte IMAP pollée.** Pour N adresses fonctionnelles distinctes
  → **N comptes**, chacun avec son **destination group** (routage natif, sans règle).
- À la création du compte (assistant New Email Account) : on choisit le **destination group**
  → tout mail entrant crée un ticket dans ce groupe.
- **Chaque groupe Zammad doit avoir une adresse d'envoi** (Manage › Groups) sinon il ne peut
  pas répondre / les triggers d'envoi sont ignorés.
- **Triggers = exceptions uniquement** (réaffectation par mot-clé, priorité, langue ;
  conditions sur Recipient/From/Subject/Body). Pas de détection de langue native fiable.

Sources : admin-docs.zammad.org (channels/email/accounts, settings, manage/trigger).

→ Règle d'or : **1 boîte o2switch = 1 compte Zammad = 1 groupe/équipe.** Le nombre d'adresses
choisi = autant de boîtes à créer **et** de comptes Zammad à configurer → rester lean.

## Schéma proposé (nommage 100 % FR, 1 mot)

| Adresse o2switch | Groupe Zammad | Rôle | Priorité POC |
|---|---|---|---|
| `devis@` | Devis / Chiffrage | Demandes de prix / RFQ — cœur du flux | essentiel |
| `commercial@` | Commercial | Relation client, suivi d'affaires | essentiel |
| `be@` | Bureau d'études | Plans, 3D, CDC technique (≈ CAD) | essentiel |
| `livraison@` | Logistique | Délais, expédition, transport | recommandé |
| `compta@` | Comptabilité | Factures, paiements, BC | recommandé |
| `sav@` | SAV / Qualité | Réclamations, NC, retours, garantie | recommandé |
| `contact@` | Général / Triage | Filet (pas de catch-all o2switch) | recommandé |
| `technique@` | Méthodes / Eng. | Faisabilité, specs, tolérances | optionnel |
| `achats@` | Achats | Sourcing, sous-traitance, fournisseurs | optionnel |

**Convention de nommage** : français, mono-mot, pas de mélange FR/EN (évite les doublons de
routage). Acronymes métier `be@`/`sav@` OK en France. Alias EN export (`quote@`→`devis@`)
seulement plus tard, en redirection, sans groupe Zammad dédié.

## ✅ Implémenté (2026-06-26)

7 groupes + 7 comptes Email configurés via `config/routing/seed_email_channels.rb` (console
Rails). Tous **IMAP ok / SMTP ok**. Mapping boîte → groupe → adresse d'envoi établi
(1 boîte = 1 compte = 1 groupe). **Routage par adresse validé** (mail → bon groupe).

**Triggers de routage fin** (`config/routing/seed_triggers.rb`, console Rails) — validés :
- ~~10–14 : re-routage du triage `contact@` par mots-clés du **sujet**~~ → **MIGRÉ vers des
  Postmaster Filters** (cf. section dédiée plus bas ; ces triggers sont désactivés).
- 20 : priorité haute sur mots d'urgence ; 30 : auto-générés → priorité basse + tag `auto` ;
  32 : interne `@vivacoda.com` → tag `internal` ; 40 : mots FR → tag `non-english`.
- **Auto-replies natifs désactivés** (pas d'accusé de réception auto).
- Tests OK : `contact@`+"Invoice…" → Accounting ; `quote@`+"URGENT…" → Quote + priorité haute.

### ✅ Test de charge routage (2026-06-26, 14 emails via Resend)

Simulation `tests/routing-sim/` → **routage critique validé end-to-end** : par adresse (7/7),
re-routage triage par mots-clés du sujet (Quote/Accounting/CAD/After-sales), priorité **URGENT**.

### ✅ Routage via Postmaster Filters (2026-07-01) — `config/routing/seed_postmaster_filters.rb`

On route désormais le triage à l'**ingestion du mail** (au parsing, **avant** la création du
ticket) via les **Postmaster Filters** de Zammad, et non plus via des triggers de mise à jour.

**Pourquoi c'est mieux ici** : les filtres tournent **hors Elasticsearch**. Ils lisent les
attributs bruts du mail (`to`, `subject`, `body`) → on peut matcher le **CORPS** de façon
**fiable** (ce que les triggers ne pouvaient pas, cf. limitation ES-async ci-dessous). Vérifié
en lisant le code Zammad 7.1.1 : `FilterProcessor` matche sur `@mail[:to|:subject|:body]` ;
opérateurs `matches regex` / `does not match regex` (le matcher force `/i`, `value.match`
non ancré → `\A` et lookahead `(?!…)` fonctionnent) ; le groupe se pose via l'en-tête
`x-zammad-ticket-group_id` (validé par `association_id_validation`), les tags via
`x-zammad-ticket-tags` (`operator: add`).

**Ce qui a été mis en place (16 filtres actifs)** :
- **Migration du triage `contact@`** (ex-triggers 10–14) → filtres par catégorie
  (quote/accounting/logistics/cad/aftersales), en **SUJET *et* CORPS** (`20x`/`30x`).
- **Rattrapage des mal-adressages** (`40x`) : un RFQ arrivé par erreur dans `accounting@`, `sales@`
  ou `support@` est re-routé vers **Quote** + tag **`rerouted:auto`** (audit / réversibilité).
- **Enrichissement** (`50/52/60`, ex-triggers 30/32/40) : expéditeur auto-généré (`from` no-reply…)
  → priorité basse + tag `auto` ; interne `@vivacoda.com` → tag `internal` ; corps non anglais
  (`body`) → tag `non-english`. **C'est ce qui lève la limite ES-async** (cf. section dédiée).
- **Nouvelles demandes seulement** (routage) : les réponses/transferts (`Re:`/`Fwd:`/`Fw:`/`Tr:`)
  sont exclus — via lookahead négatif pour les filtres SUJET, via garde `subject does not match
  ^(re|fwd…):` pour les filtres CORPS. (Les filtres d'enrichissement, eux, s'appliquent à tout mail.)

**Ordre = tri alphabétique du nom** (`Channel::Filter::Database` : `reorder(:name, :created_at)`),
le dernier `perform` sur une même clé écrase. Convention de nommage :
`20x` triage CORPS → `30x` triage SUJET (s'exécute après ⇒ **le sujet l'emporte sur le corps**)
→ `40x` mal-adressage (boîtes disjointes du triage). Les triggers 10–14 sont **désactivés** par
le seed.

**Pourquoi le reroute l'emporte sur le routage par adresse** (doc officielle `System filters.md`,
`docs/zammad-officiel/`) : le pipeline de system-filters est numéroté. Nos filtres custom tournent
à l'étape **`1000_postmaster_filter_database_check`** et posent l'en-tête `x-zammad-ticket-group_id`.
Plus tard, **`6005_postmaster_filter_identify_group`** fixe le groupe « *explicit group headers
first, then existing ticket context, then mailbox/group mapping or fallback default* ». Notre
en-tête explicite (1000) est donc honoré **avant** le mapping boîte→groupe (le routage par adresse) :
un RFQ tombé dans `accounting@` part bien vers Quote de façon **déterministe**, pas par chance.
À noter aussi (`0007–0009`) : une vraie **réponse** (`Re:`) à un ticket existant est captée comme
*follow-up* et rattachée au ticket d'origine (donc à son groupe) — ce qui conforte notre exclusion
`Re:` (on ne re-route que les nouvelles demandes).

**Vérifié** (2026-07-01) : 6 cas passés en rejouant le vrai `FilterProcessor` sur des mails
synthétiques (misaddress→Quote+tag ; `Re:` non re-routé ; triage sujet ; **sujet > corps** ;
triage corps ; mail compta légitime non touché) + test d'ingestion live via Resend.

> ⚠️ Faux-positif résiduel assumé (POC) : un mail **légitimement** comptable dont le SUJET
> contient `quotation`/`quote` (ex. « Invoice for your quotation Q-123 ») sera re-routé vers
> Quote. Le tag `rerouted:auto` rend le cas repérable et réversible par un humain. Regex
> mal-adressage volontairement bornée (`\b(quote|quotation|rfq|pricing|estimat)\b`, sans `cost`
> ni `paid`).

### ✅ Ex-limitation RÉSOLUE (2026-07-01) — conditions sur le CONTENU d'article (ES async)

> **TL;DR** : les triggers **30/32/40** (contenu `from`/`body`) sont désormais des **Postmaster
> Filters** (`50/52/60`, matchés à l'ingestion, HORS Elasticsearch). **Prouvé en live** : #41 corps
> FR → tag `non-english` ; #43 `no-reply` → tag `auto` + priorité basse. La limitation ci-dessous
> ne concerne plus aucun trigger actif — elle est conservée comme **trace historique** de la cause.

Les ex-triggers **30 (`article.from` → tag `auto`/no-reply)** et **40 (`article.body` → tag
`non-english`)** **ne se déclenchaient PAS à la création**. Cause confirmée :
- `article.subject` est mappé sur `ticket.title` (en base) → matche **sans Elasticsearch** → OK
  (re-routage + priorité marchent).
- `article.from` / `article.body` sont matchés **via Elasticsearch**, or à l'instant où les triggers
  de création tournent, l'article **n'est pas encore indexé** (indexation ES asynchrone) → la
  condition ne matche pas. (Vérifié : `SearchIndexBackend.search('Bonjour'/'no-reply')` retrouve
  bien les tickets *a posteriori*, une fois l'indexation faite.)

C'était une **limitation Zammad**, pas un défaut de notre config. **Décision initiale (2026-06-26)** :
garder les triggers 30/40 tels quels + envisager un AI Agent détecteur de langue ou un job Scheduler
décalé. **Remplacée (2026-07-01)** : la migration vers Postmaster Filters (match à l'ingestion, hors
ES) résout le cas directement — plus besoin d'AI Agent ni de Scheduler pour `auto`/`internal`/
`non-english`. Les filtres `50/52/60` posent priorité/tags via `from`/`body` ; le seed
`seed_postmaster_filters.rb` désactive les triggers 30/32/40.

Nuance routage : un mail no-reply au sujet « Automated **delivery** notification » part en Logistics
(mot-clé `delivery`) — faux-positif attendu du re-routage par mots-clés du triage.

Reste : (option) règles VIP par domaine.

## Décision figée (2026-06-26) — 7 boîtes, nommage **anglais**

| Adresse o2switch | Groupe Zammad | Rôle |
|---|---|---|
| `quote@` | Quote / Estimating | Demandes de prix / RFQ — cœur du flux |
| `sales@` | Sales | Relation client, suivi d'affaires |
| `cad@` | Engineering / CAD | Plans, 3D, CDC technique (bureau d'études) |
| `delivery@` | Logistics | Délais, expédition, transport |
| `accounting@` | Accounting | Factures, paiements, BC |
| `support@` | After-sales / Quality | Réclamations, NC, retours, garantie (SAV) |
| `contact@` | General / Triage | Filet (pas de catch-all o2switch) |

Extensions futures possibles (non créées pour l'instant) : `engineering@`/`technical@`,
`purchasing@`. Alias non-pollables → uniquement en redirection vers une vraie boîte.

### Étapes
1. **cPanel o2switch** : créer les 7 **vraies boîtes** (pas des alias), noter user + mot de passe
   de chacune (nécessaires à Zammad pour le polling IMAP).
2. Récupérer le **hostname serveur** (`serveurXXX.o2switch.net`).
3. Mettre à jour `infra/zammad/.env` (`ZAMMAD_FQDN=localhost`) et démarrer la stack.
4. Dans Zammad : créer les **7 groupes** (avec adresse d'envoi), puis **7 comptes Email**
   (IMAP 993 SSL / SMTP 465 SSL) → destination group correspondant.
5. Triggers seulement pour exceptions (mots-clés, priorité, langue).
