---
title: Service Handlers Telegram
url: https://blog.guigpap.com/fr/workflows/service-handlers/
url_md: https://blog.guigpap.com/fr/workflows/service-handlers.md
category: automation
date: '2026-05-19'
maturite: production
techno:
- n8n
- telegram
- odoo
- docker
application:
- automation
- operations
---
# Service Handlers Telegram
> Famille de 4 sub-workflows N8N qui exécutent les actions Telegram par domaine (General, Odoo, Docker, N8N)
## 1. Quoi ? — Définition et contexte
Les **Service Handlers** sont une famille de 4 sub-workflows N8N appelés par l'Orchestrateur Telegram une fois qu'une intention utilisateur a été détectée. Chacun encapsule un domaine fonctionnel (chat général, Odoo, Docker, administration N8N) et expose une interface stable à l'Orchestrateur : on lui passe `{service, action, params, chatId, userId, isAdmin}`, il retourne `{chatId, text, parseMode, keyboard}` prêt à être envoyé à Telegram.
> **Note - Pourquoi une famille de handlers**
>
> L'Orchestrateur Telegram contient déjà la logique de routage, d'authentification et de gestion des callbacks. Y empiler la logique métier de chaque domaine produirait un workflow ingérable. Les Service Handlers découpent par responsabilité : un handler par grande famille d'actions, chacun activable et debuggable indépendamment.
### Composants
| Workflow | ID | Nodes | Rôle |
|----------|-----|-------|------|
| **Service Handler - General** | `eCayuEQg4e8OhW6z` | 10 | Aide (`help`) + chat libre avec Claude (`chat`) |
| **Service Handler - Odoo** | `QFvNDnHjV6MIBB7n` | 33 | Contacts, factures, devis, opportunités, projets, taches, dashboard |
| **Service Handler - Docker** | `lBrSuWWM2WpN1v9I` | 5 | Dispatcher vers `Docker Actions` + formatage Telegram |
| **Service Handler - N8N** (parent) | `lwY2qomOWjrx0EP8` | 6 | Gate admin + délégation au sub-workflow |
| **SH N8N Action Executor** (sub) | `HpILpcfY8Z6OJq9b` | 37 | Workflows, executions, datatables, triggers (extrait via #279) |
### Position dans le pipeline Telegram
```mermaid
flowchart TD
TG["Telegram Trigger"]
Orch["Orchestrateur Telegram"]
IA["IA Router · détection d'intent"]
RBS["Route by Service"]
subgraph Handlers["Service Handlers"]
direction TB
SHG["SH General · 10n"]
SHO["SH Odoo · 33n"]
SHD["SH Docker · 5n"]
SHN["SH N8N · 6n"]
end
subgraph Subs["Sub-workflows métier"]
direction TB
Claude["CLI Ollama · /api/generate"]
Odoo["13 nodes Odoo natifs"]
DA["Docker Actions"]
SHNE["SH N8N Action Executor · 37n"]
end
TG --> Orch --> IA --> RBS
RBS -->|general| SHG --> Claude
RBS -->|odoo / projet| SHO --> Odoo
RBS -->|docker| SHD --> DA
RBS -->|n8n| SHN --> SHNE
SHG --> Send["Send Telegram Message"]
SHO --> Send
SHD --> Send
SHN --> Send
```
---
## 2. Pourquoi ? — Enjeux et motivations
### Problèmes résolus
| Problème | Sans handlers dédiés | Avec Service Handlers |
|----------|---------------------|----------------------|
| **Couplage Orchestrateur/métier** | L'Orchestrateur grossit à chaque action ajoutée | Le routage reste ~5 outputs ; le métier vit ailleurs |
| **Permissions dispersées** | Check `isAdmin` répété partout | Gate centralisée dans chaque handler sensible |
| **Tests cassants** | Tester Odoo nécessite de simuler tout le pipeline Telegram | Sub-workflow appelable isolément avec un payload |
| **Évolutivité** | Ajouter `/projet` ou des actions N8N gonfle un workflow déjà critique | Le handler concerné absorbe la croissance |
### Pourquoi extraire SH N8N Action Executor (#279)
Le `Service Handler - N8N` faisait initialement 40 nodes : gate admin + 15 actions (list_workflows, toggle, executions, datatables, triggers…). Trois symptômes ont déclenché le refactoring vers un parent fin + sub-workflow lourd :
| Symptôme | Cause |
|----------|-------|
| **Re-tests complets à chaque ajout** | Modifier une action obligeait à retester le gate admin |
| **Pagination + callback partout** | La logique pagination était dupliquée entre listes |
| **Lecture difficile** | 40 nodes dans un même canvas masquait le Switch d'actions |
Le découpage produit 6 nodes en parent (Trigger → Check Admin → Is Admin? → Restore Input → Execute Sub → Return) et 37 nodes dans `SH N8N Action Executor` qui orchestre les 15 actions N8N.
### Pourquoi SH Odoo a grandi à 33 nodes (#187)
L'ajout des commandes `/projet` a porté SH Odoo de 27 à 33 nodes : 3 nouveaux Switch outputs (`list_projects`, `create_project`, `project_status`), 5 nodes Odoo natifs supplémentaires, et un Code node d'agrégation pour le dashboard projet. C'est la limite haute supportable avant un découpage analogue à #279 ; cf. section "Et si ?".
### Conventions partagées
Tous les handlers respectent les mêmes invariants :
| Invariant | Implémentation |
|-----------|---------------|
| **Trigger unique** | `Execute Workflow Trigger` avec `inputSource: passthrough` |
| **Send Typing** | Indicateur d'activité Telegram pendant l'attente |
| **Format Response** | Sortie standard `{chatId, text, parseMode, keyboard.pattern}` |
| **Long message** | Split / pagination si > 4096 caractères |
| **`onError: continueRegularOutput`** | Sur les nodes externes (Odoo, HTTP) pour formatter les erreurs proprement |
---
## 3. Comment ? — Mise en œuvre technique
### Tableau récapitulatif
| Workflow | ID | Nodes | Rôle |
|----------|-----|-------|------|
| Service Handler - General | `eCayuEQg4e8OhW6z` | 10 | Help statique + chat Claude via CLI Ollama |
| Service Handler - Odoo | `QFvNDnHjV6MIBB7n` | 33 | 13 actions Odoo (contacts, factures, projets, dashboard) |
| Service Handler - Docker | `lBrSuWWM2WpN1v9I` | 5 | Dispatch vers `Docker Actions` + keyboards contextuels |
| Service Handler - N8N | `lwY2qomOWjrx0EP8` | 6 | Gate admin + appel sub-workflow |
| SH N8N Action Executor | `HpILpcfY8Z6OJq9b` | 37 | Exécution réelle des actions N8N (extrait #279) |
### Flux interne par handler
```mermaid
flowchart TD
Trigger["Execute Workflow Trigger
service · action · params · chatId · isAdmin"]
subgraph General["SH General · 10n"]
direction TB
Typing1["Send Typing"]
Route1["Route by Action"]
Help["Generate Help Msg"]
Chat["Call Claude /api/generate"]
Fmt1["Format Response"]
end
subgraph Odoo["SH Odoo · 33n"]
direction TB
AdminO["Check Admin"]
SwitchO["Route by Action · 13 outputs"]
Search["3x Search Contact + Merge + Dedup"]
IfInv["IF Invoice Status"]
SwPer["Switch Period · week/month/all"]
PStat["3 Odoo queries + Aggregate"]
Fmt2["Format Response"]
end
subgraph Docker["SH Docker · 5n"]
direction TB
Parse["Parse Docker Params"]
CallDA["Call Docker Actions"]
Fmt3["Format Response · keyboard pattern"]
end
subgraph N8N["SH N8N · 6n"]
direction TB
AdminN["Check Admin · DT GET"]
IsAdm["Is Admin?"]
Restore["Restore Input Data"]
CallExec["Call SH N8N Action Executor · 37n"]
end
Trigger --> General
Trigger --> Odoo
Trigger --> Docker
Trigger --> N8N
```
### Gate admin (Odoo & N8N)
Les actions Odoo et N8N sont sensibles (mutation de données, exécution de workflows). Les deux handlers interrogent la même Data Table `Telegram Authorized users` (`invf2IKyVDyzoBZr`) et coupent net en cas d'absence du flag.
| Étape | Comportement |
|-------|-------------|
| Lookup `Telegram Authorized users` par `telegram_user_id` | Récupère la ligne utilisateur |
| `is_admin` absent ou `false` | Sortie immédiate avec `⛔ Accès réservé aux administrateurs.` |
| `is_admin === true` | Continue le flux métier |
| Réponse non-admin | `parseMode: HTML`, `keyboard.pattern: no_keyboard` |
> **Caution - Toujours coupler gate + Restore Input**
>
> Le node Data Table GET du `Check Admin` écrase `$json` avec les colonnes de la table. Sans un node `Restore Input Data` qui réinjecte `$('Execute Workflow Trigger').first().json`, les nodes en aval perdent `service`, `action`, `params`, etc. Bug observé pendant #279 puis corrigé.
### Service Handler - General · 10 nodes
Deux actions : `help` (réponse statique HTML), et `chat` (fallback, appel CLI Ollama `/api/generate` avec `model: codex-yolo`, `session_id` généré). Le body de l'appel est sérialisé via `JSON.stringify` pour échapper guillemets et retours ligne — sans quoi un texte issu de Gemini Vision OCR avec citations cassait le payload (correctif `d80e5ce`).
### Service Handler - Odoo · 33 nodes
Le handler implémente 13 actions via un Switch à 13 outputs + fallback :
| Action | Modèle Odoo | Particularité |
|--------|-------------|---------------|
| `search_contact` | `res.partner` | 3 recherches parallèles (name / email / phone) + Merge + Dedup |
| `create_contact` / `update_contact` | `res.partner` | Update via Custom Resource avec champ dynamique |
| `search_invoice` | `account.move` | IF conditionnel sur `payment_state` |
| `search_quote` / `search_opportunity` | `sale.order` / `crm.lead` | Filtres simples |
| `create_opportunity` | `crm.lead` | Champs : name, email, phone, revenue, probability |
| `project_hours` | `account.analytic.line` | Switch Period (week / month / all) |
| `search_project` / `search_task` | `project.project` / `project.task` | Custom Resource + filtre `name like` |
| `list_projects` | `project.project` | Tous les projets actifs (limit 15) |
| `create_project` | `project.project` | Via `/projet create ` |
| `project_status` | 3 modèles | Chaîne : Get Project → Tasks → Timesheets → Aggregate Code |
Tous les nodes Odoo utilisent le credential `Odoo tool admin` (`xQmHuksl8E7nuwCV`), `onError: continueRegularOutput`, et `alwaysOutputData: true` (top-level) pour que `Format Response` s'exécute même sans résultat.
### Service Handler - Docker · 5 nodes
Le handler reste mince car le travail réel est délégué à `Docker Actions` (SSH vers le VPS, exécution de `docker compose`). Le node `Parse Docker Params` traduit l'intent IA Router en paire `(dockerAction, dockerStack)` :
- Passthrough si l'action est déjà valide (`status`, `restart`, `logs`, `update`, `start`, `stop`, `list-all`).
- Sinon mapping via `ACTION_MAP` (`container_status` → `status`, `restart_container` → `restart`…).
- Mapping container → stack via `CONTAINER_TO_STACK` (`n8n` → `n8n-stack`, `caddy` → `security-stack`…).
- Erreur explicite si action inconnue (au lieu d'un fallback silencieux sur `status`).
`Format Response` produit `keyboard.pattern` parmi `standard` / `critical` / `list-all` / `after-action`. L'Orchestrateur applique ensuite le bon Switch côté envoi (cf. règle « Reply Markup non expressionable »).
| Pattern | Usage | Boutons |
|---------|-------|---------|
| `standard` | Stack non-critique après status/logs | Status · Restart · Logs · Update · Retour |
| `critical` | `security-stack` (restart/stop/start/update bloqués) | Status · Logs · Retour |
| `list-all` | Vue tous containers | Status… · Restart… · Logs… · Retour |
| `after-action` | Après restart/update | Status · Logs · Restart · Retour |
### Service Handler - N8N · 6 nodes + sub-workflow 37 nodes
Le parent ne fait que (1) gate admin via Data Table, (2) restore input, (3) déléguer à `SH N8N Action Executor` via Execute Workflow. Le sub-workflow expose 15 actions :
| Famille | Actions |
|---------|---------|
| **Workflows** | `list_workflows` (paginé), `toggle_workflow` |
| **Executions** | `list_running`, `list_executions` (filtres status), `view_execution`, `retry_execution` |
| **Data Tables** | `list_datatables`, `view_datatable` (paginé), `view_row`, `edit_row`, `delete_row` |
| **Triggers manuels** | `list_triggers`, `list_trigger_cat`, `confirm_trigger`, `execute_trigger` |
Les IDs longs (>64 octets pour les callbacks Telegram) sont stockés dans la Data Table `n8n_pending_actions` avec un short ID 8 chars et un TTL de 5 minutes. Les workflows déclenchables manuellement sont déclarés dans `n8n_trigger_whitelist` (catégories `backup` / `sync` / `cleanup` / `maintenance`).
### Long response handling
Telegram impose 4096 caractères par message. Trois patterns coexistent :
| Pattern | Quand l'utiliser |
|---------|------------------|
| **Troncature** | Listes simples, dernier message contient `… N résultats` |
| **Pagination** | Listes longues type executions ou data tables — boutons `◀️ N/M ▶️` |
| **Multi-message** | Détails d'exécution avec stack trace — `multiMessage: true`, keyboard sur le dernier seulement |
### Data Tables référencées
| Data Table | ID | Utilisée par |
|------------|-----|--------------|
| `Telegram Authorized users` | `invf2IKyVDyzoBZr` | SH Odoo, SH N8N (gate admin) |
| `n8n_trigger_whitelist` | — | SH N8N Action Executor (triggers manuels) |
| `n8n_pending_actions` | — | SH N8N Action Executor (short ID ↔ payload) |
| `n8n_datatable_metadata` | — | SH N8N Action Executor (tables exposées) |
### Interface d'entrée / sortie standard
**Entrée (depuis l'Orchestrateur) :**
```json
{
"service": "odoo",
"action": "search_contact",
"params": { "query": "Dupont" },
"chatId": 123456789,
"userId": 123456789,
"isAdmin": true,
"originalText": "Cherche le contact Dupont"
}
```
**Sortie (vers l'Orchestrateur) :**
```json
{
"chatId": 123456789,
"text": "📇 Contacts (2 résultats)...",
"parseMode": "HTML",
"keyboard": { "pattern": "odoo_contacts" }
}
```
L'Orchestrateur ne lit que `text`, `parseMode`, et `keyboard.pattern` pour router vers le bon node Telegram (la structure du Reply Markup est hardcodée par pattern dans l'UI — N8N ne supporte pas les expressions sur la structure du keyboard, seulement sur les valeurs des boutons).
---
## 4. Et si ? — Perspectives et limites
### Limites actuelles
| Limite | Impact | Mitigation |
|--------|--------|-----------|
| **SH Odoo à 33 nodes** | Lecture du canvas devient pénible | Découpage prévu (par famille d'actions) sur le modèle de #279 |
| **SH General sans mémoire** | Le chat libre n'a pas d'historique multi-tours | Conversation Agent dédié pour les conversations multi-turn |
| **SH Docker dépend de SSH** | Toute panne SSH bloque toutes les actions Docker | `Docker Actions` retourne success/error, le handler formate l'erreur |
| **Gate admin = column lookup** | Pas de RBAC fin (juste admin / non-admin) | Suffisant pour usage solo, à élargir si multi-utilisateurs |
| **`Reply Markup` non expressionable** | Chaque pattern nécessite un node Telegram dédié | Pattern Switch + keyboards hardcodés (cf. doc telegram orchestrator) |
### Scénarios d'évolution
**Si SH Odoo dépasse 40 nodes** :
- Découper par famille (`SH Odoo Contacts`, `SH Odoo Projects`, `SH Odoo Sales`) sur le modèle #279
- Garder le parent comme dispatcher de gate admin + Switch initial
- Bénéfice : tests indépendants, ajout d'actions sans toucher au reste
**Si besoin de permissions plus fines** :
- Ajouter des colonnes à `Telegram Authorized users` (`can_docker`, `can_odoo`, `can_n8n`)
- Remplacer le check binaire `is_admin` par un check capability-based dans chaque handler
- Garder la rétrocompatibilité : `is_admin = true` implique toutes les capacités
**Si volume de chat Claude augmente** :
- SH General reste single-turn (utilité limitée pour conversation continue)
- Router proactivement vers Conversation Agent au-delà de N messages dans la même fenêtre
- Cache de session côté CLI Ollama via Redis (déjà en place)
**Si nouveau domaine (ex. monitoring direct)** :
- Créer `Service Handler - Monitoring` (Prometheus queries, Grafana dashboards)
- Ajouter un output `monitoring` dans Route by Service de l'Orchestrateur
- L'IA Router apprend la nouvelle catégorie via examples dans son prompt
> **Tip - Critère de découpage**
>
> On extrait un sub-workflow quand (1) la lisibilité du canvas devient bloquante, (2) une partie du flux a sa propre logique de retry / pagination, ou (3) les tests deviennent couplés à l'ensemble. SH N8N a franchi les trois critères et a été refactorisé en #279.
---
## Pages liées
### Workflows
- [Telegram Orchestrator](/fr/workflows/telegram-orchestrator/) — Appelant des Service Handlers
- [IA Router & MCP](/fr/workflows/ia-router-mcp/) — Détection d'intent en amont
- [Notification Hub](/fr/workflows/notification-hub/) — Pattern de routage analogue côté sortie
- [Docker Updates](/fr/workflows/docker-updates/) — Consommateur du flux Docker
- [GitHub-Odoo Sync](/fr/workflows/github-odoo-sync/) — Autre famille de sub-workflows extraits
### Infrastructure
- [Database Backup](/fr/infrastructure/database-backup/) — Sauvegardes consultables via SH N8N triggers
## Metadonnees agent
- Cet article est issu du blog GuiGPaP Lab.
- Contexte global du blog: https://blog.guigpap.com/llms.txt
- Contact auteur: https://odoo.guigpap.com/mon-cv
- Licence: CC-BY-SA 4.0